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  }