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  }