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  }