github.com/magodo/terraform@v0.11.12-beta1/helper/signalwrapper/wrapper.go (about)

     1  // Package signalwrapper is used to run functions that are sensitive to
     2  // signals that may be received from outside the process. It can also be
     3  // used as just an async function runner that is cancellable and we may
     4  // abstract this further into another package in the future.
     5  package signalwrapper
     6  
     7  import (
     8  	"log"
     9  	"os"
    10  	"os/signal"
    11  	"sync"
    12  )
    13  
    14  // CancellableFunc is a function that cancels if it receives a message
    15  // on the given channel. It must return an error if any occurred. It should
    16  // return no error if it was cancelled successfully since it is assumed
    17  // that this function will probably be called again at some future point
    18  // since it was interrupted.
    19  type CancellableFunc func(<-chan struct{}) error
    20  
    21  // Run wraps and runs the given cancellable function and returns the Wrapped
    22  // struct that can be used to listen for events, cancel on other events
    23  // (such as timeouts), etc.
    24  func Run(f CancellableFunc) *Wrapped {
    25  	// Determine the signals we're listening to. Prematurely making
    26  	// this a slice since I predict a future where we'll add others and
    27  	// the complexity in doing so is low.
    28  	signals := []os.Signal{os.Interrupt}
    29  
    30  	// Register a listener for the signals
    31  	sigCh := make(chan os.Signal, 1)
    32  	signal.Notify(sigCh, signals...)
    33  
    34  	// Create the channel we'll use to "cancel"
    35  	cancelCh := make(chan struct{})
    36  
    37  	// This channel keeps track of whether the function we're running
    38  	// completed successfully and the errors it may have had. It is
    39  	// VERY IMPORTANT that the errCh is buffered to at least 1 so that
    40  	// it doesn't block when finishing.
    41  	doneCh := make(chan struct{})
    42  	errCh := make(chan error, 1)
    43  
    44  	// Build our wrapped result
    45  	wrapped := &Wrapped{
    46  		ErrCh:    errCh,
    47  		errCh:    errCh,
    48  		cancelCh: cancelCh,
    49  	}
    50  
    51  	// Start the function
    52  	go func() {
    53  		log.Printf("[DEBUG] signalwrapper: executing wrapped function")
    54  		err := f(cancelCh)
    55  
    56  		// Close the done channel _before_ sending the error in case
    57  		// the error channel read blocks (it shouldn't) to avoid interrupts
    58  		// doing anything.
    59  		close(doneCh)
    60  
    61  		// Mark completion
    62  		log.Printf("[DEBUG] signalwrapper: wrapped function execution ended")
    63  		wrapped.done(err)
    64  	}()
    65  
    66  	// Goroutine to track interrupts and make sure we do at-most-once
    67  	// delivery of an interrupt since we're using a channel.
    68  	go func() {
    69  		// Clean up after this since this is the only function that
    70  		// reads signals.
    71  		defer signal.Stop(sigCh)
    72  
    73  		select {
    74  		case <-doneCh:
    75  			// Everything happened naturally
    76  		case <-sigCh:
    77  			log.Printf("[DEBUG] signalwrapper: signal received, cancelling wrapped function")
    78  
    79  			// Stop the function. Goroutine since we don't care about
    80  			// the result and we'd like to end this goroutine as soon
    81  			// as possible to avoid any more signals coming in.
    82  			go wrapped.Cancel()
    83  		}
    84  	}()
    85  
    86  	return wrapped
    87  }
    88  
    89  // Wrapped is the return value of wrapping a function. This has channels
    90  // that can be used to wait for a result as well as functions to help with
    91  // different behaviors.
    92  type Wrapped struct {
    93  	// Set and consumed by user
    94  
    95  	// ErrCh is the channel to listen for real-time events on the wrapped
    96  	// function. A nil error sent means the execution completed without error.
    97  	// This is an exactly once delivery channel.
    98  	ErrCh <-chan error
    99  
   100  	// Set by creator
   101  	errCh    chan<- error
   102  	cancelCh chan<- struct{}
   103  
   104  	// Set automatically
   105  	once       sync.Once
   106  	cancelCond *sync.Cond
   107  	cancelLock *sync.Mutex
   108  	resultErr  error
   109  	resultSet  bool
   110  }
   111  
   112  // Cancel stops the running function and blocks until it returns. The
   113  // resulting value is returned.
   114  //
   115  // It is safe to call this multiple times. This will return the resulting
   116  // error value each time.
   117  func (w *Wrapped) Cancel() error {
   118  	w.once.Do(w.init)
   119  	w.cancelLock.Lock()
   120  
   121  	// If we have a result set, return that
   122  	if w.resultSet {
   123  		w.cancelLock.Unlock()
   124  		return w.resultErr
   125  	}
   126  
   127  	// If we have a cancel channel, close it to signal and set it to
   128  	// nil so we never do that again.
   129  	if w.cancelCh != nil {
   130  		close(w.cancelCh)
   131  		w.cancelCh = nil
   132  	}
   133  
   134  	// Wait for the result to be set
   135  	defer w.cancelLock.Unlock()
   136  	w.cancelCond.Wait()
   137  	return w.resultErr
   138  }
   139  
   140  // Wait waits for the completion of the wrapped function and returns the result.
   141  //
   142  // This can be called multiple times safely.
   143  func (w *Wrapped) Wait() error {
   144  	w.once.Do(w.init)
   145  	w.cancelLock.Lock()
   146  	defer w.cancelLock.Unlock()
   147  
   148  	// If we don't have a result yet, wait for that
   149  	if !w.resultSet {
   150  		w.cancelCond.Wait()
   151  	}
   152  
   153  	// Return the result
   154  	return w.resultErr
   155  }
   156  
   157  // done marks this wrapped function as done with the resulting value.
   158  // This must only be called once.
   159  func (w *Wrapped) done(err error) {
   160  	w.once.Do(w.init)
   161  	w.cancelLock.Lock()
   162  
   163  	// Set the result
   164  	w.resultErr = err
   165  	w.resultSet = true
   166  
   167  	// Notify any waiters
   168  	w.cancelCond.Broadcast()
   169  
   170  	// Unlock since the next call can be blocking
   171  	w.cancelLock.Unlock()
   172  
   173  	// Notify any channel listeners
   174  	w.errCh <- err
   175  }
   176  
   177  func (w *Wrapped) init() {
   178  	// Create the condition variable
   179  	var m sync.Mutex
   180  	w.cancelCond = sync.NewCond(&m)
   181  	w.cancelLock = &m
   182  }