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