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 }