github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/unit.go (about) 1 package engine 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 ) 8 9 // Unit handles synchronization management, startup, and shutdown for engines. 10 // New components should use component.ComponentManager rather than Unit. 11 type Unit struct { 12 admitLock sync.Mutex // used for synchronizing context cancellation with work admittance 13 14 wg sync.WaitGroup // tracks in-progress functions 15 ctx context.Context // context that is cancelled when the unit is Done 16 cancel context.CancelFunc // cancels the context 17 sync.Mutex // can be used to synchronize the engine 18 } 19 20 // NewUnit returns a new unit. 21 func NewUnit() *Unit { 22 23 ctx, cancel := context.WithCancel(context.Background()) 24 unit := &Unit{ 25 ctx: ctx, 26 cancel: cancel, 27 } 28 return unit 29 } 30 31 func (u *Unit) admit() bool { 32 u.admitLock.Lock() 33 defer u.admitLock.Unlock() 34 35 select { 36 case <-u.ctx.Done(): 37 return false 38 default: 39 } 40 41 u.wg.Add(1) 42 return true 43 } 44 45 func (u *Unit) stopAdmitting() { 46 u.admitLock.Lock() 47 defer u.admitLock.Unlock() 48 49 u.cancel() 50 } 51 52 // Do synchronously executes the input function f unless the unit has shut down. 53 // It returns the result of f. If f is executed, the unit will not shut down 54 // until after f returns. 55 func (u *Unit) Do(f func() error) error { 56 if !u.admit() { 57 return nil 58 } 59 60 defer u.wg.Done() 61 return f() 62 } 63 64 // Launch asynchronously executes the input function unless the unit has shut 65 // down. If f is executed, the unit will not shut down until after f returns. 66 func (u *Unit) Launch(f func()) { 67 if !u.admit() { 68 return 69 } 70 71 go func() { 72 defer u.wg.Done() 73 f() 74 }() 75 } 76 77 // LaunchAfter asynchronously executes the input function after a certain delay 78 // unless the unit has shut down. 79 func (u *Unit) LaunchAfter(delay time.Duration, f func()) { 80 u.Launch(func() { 81 select { 82 case <-u.ctx.Done(): 83 return 84 case <-time.After(delay): 85 f() 86 } 87 }) 88 } 89 90 // LaunchPeriodically asynchronously executes the input function on `interval` periods 91 // unless the unit has shut down. 92 // If f is executed, the unit will not shut down until after f returns. 93 func (u *Unit) LaunchPeriodically(f func(), interval time.Duration, delay time.Duration) { 94 u.Launch(func() { 95 ticker := time.NewTicker(interval) 96 defer ticker.Stop() 97 98 select { 99 case <-u.ctx.Done(): 100 return 101 case <-time.After(delay): 102 } 103 104 for { 105 select { 106 case <-u.ctx.Done(): 107 return 108 default: 109 } 110 111 select { 112 case <-u.ctx.Done(): 113 return 114 case <-ticker.C: 115 f() 116 } 117 } 118 }) 119 } 120 121 // Ready returns a channel that is closed when the unit is ready. A unit is 122 // ready when the series of "check" functions are executed. 123 // 124 // The engine using the unit is responsible for defining these check functions 125 // as required. 126 func (u *Unit) Ready(checks ...func()) <-chan struct{} { 127 ready := make(chan struct{}) 128 go func() { 129 for _, check := range checks { 130 check() 131 } 132 close(ready) 133 }() 134 return ready 135 } 136 137 // Ctx returns a context with the same lifecycle scope as the unit. In particular, 138 // it is cancelled when Done is called, so it can be used as the parent context 139 // for processes spawned by any engine whose lifecycle is managed by a unit. 140 func (u *Unit) Ctx() context.Context { 141 return u.ctx 142 } 143 144 // Quit returns a channel that is closed when the unit begins to shut down. 145 func (u *Unit) Quit() <-chan struct{} { 146 return u.ctx.Done() 147 } 148 149 // Done returns a channel that is closed when the unit is done. A unit is done 150 // when (i) the series of "action" functions are executed and (ii) all pending 151 // functions invoked with `Do` or `Launch` have completed. 152 // 153 // The engine using the unit is responsible for defining these action functions 154 // as required. 155 func (u *Unit) Done(actions ...func()) <-chan struct{} { 156 done := make(chan struct{}) 157 go func() { 158 u.stopAdmitting() 159 for _, action := range actions { 160 action() 161 } 162 u.wg.Wait() 163 close(done) 164 }() 165 return done 166 }