github.com/Jeffail/benthos/v3@v3.65.0/internal/shutdown/signaller.go (about)

     1  package shutdown
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  )
     8  
     9  const longTermWait = time.Hour * 24
    10  
    11  // MaximumShutdownWait is a magic number determining the maximum length of time
    12  // that a component should be willing to wait for a child to finish shutting
    13  // down before it can give up and exit.
    14  //
    15  // This wait time is largely symbolic, if a component blocks for anything more
    16  // than a few minutes then it has failed in its duty to gracefully terminate.
    17  //
    18  // However, it's still necessary for components to provide some measure of time
    19  // that they're willing to wait for with the current mechanism (WaitForClose),
    20  // therefore we provide a very large duration, and since this is a magic number
    21  // I've defined it once and exposed as a function, allowing us to more easily
    22  // identify these cases and refactor them in the future.
    23  func MaximumShutdownWait() time.Duration {
    24  	return longTermWait
    25  }
    26  
    27  // Signaller is a mechanism owned by components that support graceful
    28  // shut down and is used as a way to signal from outside that any goroutines
    29  // owned by the component should begin to close.
    30  //
    31  // Shutting down can happen in two tiers of urgency, the first is to terminate
    32  // "at leisure", meaning if you're in the middle of something it's okay to do
    33  // that first before terminating, but please do not commit to new work.
    34  //
    35  // The second tier is immediate, where you need to clean up resources and
    36  // terminate as soon as possible, regardless of any tasks that you are currently
    37  // attempting to finish.
    38  //
    39  // Finally, there is also a signal of having closed down, which is made by the
    40  // component and can be used from outside to determine whether the component
    41  // has finished terminating.
    42  type Signaller struct {
    43  	closeAtLeisureChan chan struct{}
    44  	closeAtLeisureOnce sync.Once
    45  
    46  	closeNowChan chan struct{}
    47  	closeNowOnce sync.Once
    48  
    49  	hasClosedChan chan struct{}
    50  	hasClosedOnce sync.Once
    51  }
    52  
    53  // NewSignaller creates a new signaller.
    54  func NewSignaller() *Signaller {
    55  	return &Signaller{
    56  		closeAtLeisureChan: make(chan struct{}),
    57  		closeNowChan:       make(chan struct{}),
    58  		hasClosedChan:      make(chan struct{}),
    59  	}
    60  }
    61  
    62  // CloseAtLeisure signals to the owner of this Signaller that it should
    63  // terminate at its own leisure, meaning it's okay to complete any tasks that
    64  // are in progress but no new work should be started.
    65  func (s *Signaller) CloseAtLeisure() {
    66  	s.closeAtLeisureOnce.Do(func() {
    67  		close(s.closeAtLeisureChan)
    68  	})
    69  }
    70  
    71  // CloseNow signals to the owner of this Signaller that it should terminate
    72  // right now regardless of any in progress tasks.
    73  func (s *Signaller) CloseNow() {
    74  	s.CloseAtLeisure()
    75  	s.closeNowOnce.Do(func() {
    76  		close(s.closeNowChan)
    77  	})
    78  }
    79  
    80  // ShutdownComplete is a signal made by the component that it and all of its
    81  // owned resources have terminated.
    82  func (s *Signaller) ShutdownComplete() {
    83  	s.hasClosedOnce.Do(func() {
    84  		close(s.hasClosedChan)
    85  	})
    86  }
    87  
    88  //------------------------------------------------------------------------------
    89  
    90  // ShouldCloseAtLeisure returns true if the signaller has received the signal to
    91  // shut down at leisure or immediately.
    92  func (s *Signaller) ShouldCloseAtLeisure() bool {
    93  	select {
    94  	case <-s.CloseAtLeisureChan():
    95  		return true
    96  	default:
    97  	}
    98  	return false
    99  }
   100  
   101  // CloseAtLeisureChan returns a channel that will be closed when the signal to
   102  // shut down either at leisure or immediately has been made.
   103  func (s *Signaller) CloseAtLeisureChan() <-chan struct{} {
   104  	return s.closeAtLeisureChan
   105  }
   106  
   107  // CloseAtLeisureCtx returns a context.Context that will be terminated when
   108  // either the provided context is cancelled or the signal to shut down
   109  // either at leisure or immediately has been made.
   110  func (s *Signaller) CloseAtLeisureCtx(ctx context.Context) (context.Context, context.CancelFunc) {
   111  	var cancel context.CancelFunc
   112  	ctx, cancel = context.WithCancel(ctx)
   113  	go func() {
   114  		select {
   115  		case <-ctx.Done():
   116  		case <-s.closeAtLeisureChan:
   117  		}
   118  		cancel()
   119  	}()
   120  	return ctx, cancel
   121  }
   122  
   123  // ShouldCloseNow returns true if the signaller has received the signal to shut
   124  // down immediately.
   125  func (s *Signaller) ShouldCloseNow() bool {
   126  	select {
   127  	case <-s.CloseNowChan():
   128  		return true
   129  	default:
   130  	}
   131  	return false
   132  }
   133  
   134  // CloseNowChan returns a channel that will be closed when the signal to shut
   135  // down immediately has been made.
   136  func (s *Signaller) CloseNowChan() <-chan struct{} {
   137  	return s.closeNowChan
   138  }
   139  
   140  // CloseNowCtx returns a context.Context that will be terminated when either the
   141  // provided context is cancelled or the signal to shut down immediately has been
   142  // made.
   143  func (s *Signaller) CloseNowCtx(ctx context.Context) (context.Context, context.CancelFunc) {
   144  	var cancel context.CancelFunc
   145  	ctx, cancel = context.WithCancel(ctx)
   146  	go func() {
   147  		select {
   148  		case <-ctx.Done():
   149  		case <-s.closeNowChan:
   150  		}
   151  		cancel()
   152  	}()
   153  	return ctx, cancel
   154  }
   155  
   156  // HasClosed returns true if the signaller has received the signal that the
   157  // component has terminated.
   158  func (s *Signaller) HasClosed() bool {
   159  	select {
   160  	case <-s.HasClosedChan():
   161  		return true
   162  	default:
   163  	}
   164  	return false
   165  }
   166  
   167  // HasClosedChan returns a channel that will be closed when the signal that the
   168  // component has terminated has been made.
   169  func (s *Signaller) HasClosedChan() <-chan struct{} {
   170  	return s.hasClosedChan
   171  }
   172  
   173  // HasClosedCtx returns a context.Context that will be cancelled when either the
   174  // provided context is cancelled or the signal that the component has shut down
   175  // has been made.
   176  func (s *Signaller) HasClosedCtx(ctx context.Context) (context.Context, context.CancelFunc) {
   177  	var cancel context.CancelFunc
   178  	ctx, cancel = context.WithCancel(ctx)
   179  	go func() {
   180  		select {
   181  		case <-ctx.Done():
   182  		case <-s.hasClosedChan:
   183  		}
   184  		cancel()
   185  	}()
   186  	return ctx, cancel
   187  }