github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/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 }