| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | package nowish | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	"sync/atomic" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Timeout provides a reusable structure for enforcing timeouts with a cancel | 
					
						
							| 
									
										
										
										
											2021-09-13 09:33:01 +01:00
										 |  |  | type Timeout struct { | 
					
						
							|  |  |  | 	noCopy noCopy //nolint noCopy because a copy will mess with atomics | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	tk *time.Timer    // tk is the underlying timeout-timer | 
					
						
							|  |  |  | 	ch syncer         // ch is the cancel synchronization channel | 
					
						
							|  |  |  | 	wg sync.WaitGroup // wg is the waitgroup to hold .Start() until timeout goroutine started | 
					
						
							|  |  |  | 	st timeoutState   // st stores the current timeout state (and protects concurrent use) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewTimeout returns a new Timeout instance | 
					
						
							|  |  |  | func NewTimeout() Timeout { | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	tk := time.NewTimer(time.Minute) | 
					
						
							| 
									
										
										
										
											2021-09-13 09:33:01 +01:00
										 |  |  | 	tk.Stop() // don't keep it running | 
					
						
							|  |  |  | 	return Timeout{ | 
					
						
							|  |  |  | 		tk: tk, | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 		ch: make(syncer), | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | func (t *Timeout) runTimeout(hook func()) { | 
					
						
							|  |  |  | 	t.wg.Add(1) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	go func() { | 
					
						
							|  |  |  | 		cancelled := false | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 		// Signal started | 
					
						
							|  |  |  | 		t.wg.Done() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 		select { | 
					
						
							|  |  |  | 		// Timeout reached | 
					
						
							|  |  |  | 		case <-t.tk.C: | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 			if !t.st.stop() /* a sneaky cancel! */ { | 
					
						
							|  |  |  | 				t.ch.recv() | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 				cancelled = true | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 				defer t.ch.send() | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Cancel called | 
					
						
							|  |  |  | 		case <-t.ch: | 
					
						
							|  |  |  | 			cancelled = true | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 			defer t.ch.send() | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 		// Ensure timer stopped | 
					
						
							|  |  |  | 		if cancelled && !t.tk.Stop() { | 
					
						
							|  |  |  | 			<-t.tk.C | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Defer reset state | 
					
						
							|  |  |  | 		defer t.st.reset() | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// If timed out call hook | 
					
						
							|  |  |  | 		if !cancelled { | 
					
						
							|  |  |  | 			hook() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	t.wg.Wait() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Start starts the timer with supplied timeout. If timeout is reached before | 
					
						
							|  |  |  | // cancel then supplied timeout hook will be called. Error may be called if | 
					
						
							|  |  |  | // Timeout is already running when this function is called | 
					
						
							|  |  |  | func (t *Timeout) Start(d time.Duration, hook func()) { | 
					
						
							|  |  |  | 	if !t.st.start() { | 
					
						
							|  |  |  | 		panic("nowish: timeout already started") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.runTimeout(hook) | 
					
						
							|  |  |  | 	t.tk.Reset(d) | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-13 09:33:01 +01:00
										 |  |  | // Cancel cancels the currently running timer. If a cancel is achieved, then | 
					
						
							|  |  |  | // this function will return after the timeout goroutine is finished | 
					
						
							|  |  |  | func (t *Timeout) Cancel() { | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | 	if !t.st.stop() { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 	t.ch.send() | 
					
						
							|  |  |  | 	t.ch.recv() | 
					
						
							| 
									
										
										
										
											2021-09-11 20:12:47 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-09-13 09:33:01 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // timeoutState provides a thread-safe timeout state mechanism | 
					
						
							|  |  |  | type timeoutState uint32 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // start attempts to start the state, must be already reset, returns success | 
					
						
							|  |  |  | func (t *timeoutState) start() bool { | 
					
						
							|  |  |  | 	return atomic.CompareAndSwapUint32((*uint32)(t), 0, 1) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // stop attempts to stop the state, must already be started, returns success | 
					
						
							|  |  |  | func (t *timeoutState) stop() bool { | 
					
						
							|  |  |  | 	return atomic.CompareAndSwapUint32((*uint32)(t), 1, 2) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // reset is fairly self explanatory | 
					
						
							|  |  |  | func (t *timeoutState) reset() { | 
					
						
							|  |  |  | 	atomic.StoreUint32((*uint32)(t), 0) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-13 12:29:08 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // syncer provides helpful receiver methods for a synchronization channel | 
					
						
							|  |  |  | type syncer (chan struct{}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // send blocks on sending an empty value down channel | 
					
						
							|  |  |  | func (s syncer) send() { | 
					
						
							|  |  |  | 	s <- struct{}{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // recv blocks on receiving (and dropping) empty value from channel | 
					
						
							|  |  |  | func (s syncer) recv() { | 
					
						
							|  |  |  | 	<-s | 
					
						
							|  |  |  | } |