github.com/influx6/npkg@v0.8.8/nchain/futurechain.go (about) 1 package nchain 2 3 import ( 4 "context" 5 6 "golang.org/x/sync/errgroup" 7 ) 8 9 // FutureChain implements a pure Future implementation which 10 // will lunch an operation in a go-routine but block till 11 // that operation finishes. It is created to allow 12 // chaining next operation over a linear call stack. 13 // 14 // FutureChain relies on errgroup.Group underneath. 15 // 16 type FutureChain struct { 17 ctx context.Context 18 g *errgroup.Group 19 fn func(context.Context) error 20 21 error chan error 22 closer chan struct{} 23 signal chan struct{} 24 revSignal chan struct{} 25 } 26 27 func noWork(_ context.Context) error { return nil } 28 29 // NoWorkChain returns a new FutureChain which has returns nil immediately 30 // for it's work function. This allows creating new chains that already are 31 // resolved to be used to chain other incoming work. 32 func NoWorkChain(ctx context.Context) *FutureChain { 33 return NewFutureChain(ctx, noWork) 34 } 35 36 // NewFutureChain returns a new instance of a FutureChain using provided function. 37 // It immediately lunches function into future chain error group. 38 func NewFutureChain(ctx context.Context, fx func(context.Context) error) *FutureChain { 39 chain := newFutureChain(ctx, fx) 40 chain.exec(nil, true) 41 return chain 42 } 43 44 // DeferredChain returns an new Future where it's operation will not be started 45 // immediately but will be initiated by another completed Future chain. 46 // This is useful if you wish to reduce callback trees and instead have 47 // specific instantiated FutureChains that can be passed to one another. 48 // This is also useful for deferred calculations that will be performed 49 // based on the completion of some other future yet to be created. 50 func DeferredChain(ctx context.Context, fx func(context.Context) error) *FutureChain { 51 chain := newFutureChain(ctx, fx) 52 return chain 53 } 54 55 // NewFutureChain returns a new instance of a FutureChain using provided function. 56 func newFutureChain(ctx context.Context, fx func(context.Context) error) *FutureChain { 57 ew, ctx := errgroup.WithContext(ctx) 58 chain := &FutureChain{ 59 g: ew, 60 ctx: ctx, 61 fn: fx, 62 error: make(chan error, 0), 63 closer: make(chan struct{}, 0), 64 signal: make(chan struct{}, 0), 65 revSignal: make(chan struct{}, 0), 66 } 67 68 // schedule error kicker. 69 chain.g.Go(func() error { 70 defer func() { 71 chain.revSignal <- struct{}{} 72 }() 73 return <-chain.error 74 }) 75 76 // schedule function execution. 77 chain.Go(fx) 78 79 return chain 80 } 81 82 // Go just adds another function into this current futures 83 // wait group, it behaves exactly like the errgroup.Group.Go 84 // method. 85 // 86 // It returns itself, to allow chaining. 87 func (f *FutureChain) Go(fx func(context.Context) error) *FutureChain { 88 f.g.Go(func() error { 89 <-f.closer 90 select { 91 case <-f.signal: 92 return fx(f.ctx) 93 default: 94 return nil 95 } 96 }) 97 return f 98 } 99 100 // When chains the next function call when this future has completed 101 // without having an error, else will just pass occurred error down 102 // the chain. 103 // 104 // It always returns a new FutureChain instance. 105 // 106 func (f *FutureChain) When(fx func(context.Context) error) *FutureChain { 107 newChain := newFutureChain(f.ctx, fx) 108 newChain.execIfNoError(f) 109 return newChain 110 } 111 112 // WhenFuture maps giving FutureChain to be called when this future 113 // has executed successfully with no error else, passing down occurred error. 114 // 115 // It always returns its self for chaining. 116 // 117 func (f *FutureChain) WhenFuture(fm *FutureChain) *FutureChain { 118 f.execChildIfNoError(fm) 119 return f 120 } 121 122 // Then chains the next function call regardless of the failure of 123 // this future in the chain. It will get executed and the giving 124 // occurred error from this chain will be passed down the chain 125 // regardless of any error occurring from the function passed to the 126 // returned future (i.e it always returns parent's error down the chain). 127 // 128 // It always returns a new FutureChain instance. 129 // 130 func (f *FutureChain) Then(fx func(context.Context) error) *FutureChain { 131 newChain := newFutureChain(f.ctx, fx) 132 newChain.execBranchDownError(f) 133 return newChain 134 } 135 136 // ThenFuture maps giving FutureChain to be called when this future 137 // has executed successfully with or without an error, if there is an 138 // error then the error is passed down to the provided future but the 139 // provided future will still be executed. 140 // 141 // It always returns its self for chaining. 142 // 143 func (f *FutureChain) ThenFuture(fm *FutureChain) *FutureChain { 144 f.execChildEvenIfErrorWithPassDown(fm) 145 return f 146 } 147 148 // Chain chains the next function call regardless of the failure of 149 // this future in the chain. It will get executed after completion of 150 // this future and the giving occurred error from this chain will be ignored. 151 // This forces you to keep a reference to this future chain to retrieve the 152 // error that occurred for this chain as the returned future will only ever 153 // return an error that occurred from calling the function passed in. 154 func (f *FutureChain) Chain(fx func(context.Context) error) *FutureChain { 155 newChain := newFutureChain(f.ctx, fx) 156 newChain.execNoBranchDownError(f) 157 return newChain 158 } 159 160 // ChainFuture maps giving FutureChain to be called when this future 161 // has executed successfully with or without an error, if there is an 162 // error then the error is still not passed down to the provided future 163 // and but the provided future will be executed. 164 // 165 // It always returns its self for chaining. 166 // 167 func (f *FutureChain) ChainFuture(fm *FutureChain) *FutureChain { 168 f.execChildEvenIfError(fm) 169 return f 170 } 171 172 // Wait blocks till the operation is completed and 173 // returns error seen. 174 func (f *FutureChain) Wait() error { 175 return f.g.Wait() 176 } 177 178 // exec launches function within future chain error group. 179 func (f *FutureChain) exec(err error, execFunc bool) { 180 f.error <- err 181 <-f.revSignal 182 if execFunc { 183 close(f.signal) 184 } 185 close(f.closer) 186 } 187 188 // execIfNoError will execute future chains function if parent chain 189 // has no error occurred else returns parent error down the chain. 190 // 191 // It will wait until parent future has finished execution. 192 func (f *FutureChain) execIfNoError(parent *FutureChain) { 193 go func() { 194 err := parent.Wait() 195 f.exec(err, err == nil) 196 }() 197 } 198 199 // execChildEvenIfError will execute provided chain if giving chain 200 // has no error. 201 func (f *FutureChain) execChildIfNoError(child *FutureChain) { 202 go func() { 203 err := f.Wait() 204 child.exec(err, err == nil) 205 }() 206 } 207 208 // execChildEvenIfError will execute provided chain even if giving chain 209 // had an error but will not pass down parents error. 210 func (f *FutureChain) execChildEvenIfError(child *FutureChain) { 211 go func() { 212 f.Wait() 213 child.exec(nil, true) 214 }() 215 } 216 217 // execChildEvenIfErrorWithPassDown will execute provided chain even if giving chain 218 // had an error and will pass down parents error. 219 func (f *FutureChain) execChildEvenIfErrorWithPassDown(child *FutureChain) { 220 go func() { 221 parentErr := f.Wait() 222 child.exec(parentErr, true) 223 }() 224 } 225 226 // execBranchDownError will execute future chains function even if parent chain 227 // has an error but it returns parent error down the chain even if chain function 228 // had an error occur. 229 // 230 // It will wait until parent future has finished execution. 231 func (f *FutureChain) execBranchDownError(parent *FutureChain) { 232 go func() { 233 err := parent.Wait() 234 f.exec(err, true) 235 }() 236 } 237 238 // execNoBranchDownError will execute future chains function even if parent chain 239 // has an error, it will only ever pass down error from child function. 240 // 241 // It will wait until parent future has finished execution. 242 func (f *FutureChain) execNoBranchDownError(parent *FutureChain) { 243 go func() { 244 parent.Wait() 245 f.exec(nil, true) 246 }() 247 }