github.com/MetalBlockchain/metalgo@v1.11.9/utils/timer/timer.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package timer 5 6 import ( 7 "sync" 8 "time" 9 ) 10 11 // Timer wraps a timer object. This allows a user to specify a handler. Once 12 // specifying the handler, the dispatch thread can be called. The dispatcher 13 // will only return after calling Stop. SetTimeoutIn will result in calling the 14 // handler in the specified amount of time. 15 type Timer struct { 16 handler func() 17 timeout chan struct{} 18 19 lock sync.Mutex 20 wg sync.WaitGroup 21 finished, shouldExecute bool 22 duration time.Duration 23 } 24 25 // NewTimer creates a new timer object 26 func NewTimer(handler func()) *Timer { 27 timer := &Timer{ 28 handler: handler, 29 timeout: make(chan struct{}, 1), 30 } 31 timer.wg.Add(1) 32 33 return timer 34 } 35 36 // SetTimeoutIn will set the timer to fire the handler in [duration] 37 func (t *Timer) SetTimeoutIn(duration time.Duration) { 38 t.lock.Lock() 39 defer t.lock.Unlock() 40 41 t.duration = duration 42 t.shouldExecute = true 43 t.reset() 44 } 45 46 // Cancel the currently scheduled event 47 func (t *Timer) Cancel() { 48 t.lock.Lock() 49 defer t.lock.Unlock() 50 51 t.shouldExecute = false 52 t.reset() 53 } 54 55 // Stop this timer from executing any more. 56 func (t *Timer) Stop() { 57 t.lock.Lock() 58 if !t.finished { 59 defer t.wg.Wait() 60 } 61 defer t.lock.Unlock() 62 63 t.finished = true 64 t.reset() 65 } 66 67 func (t *Timer) Dispatch() { 68 t.lock.Lock() 69 defer t.lock.Unlock() 70 defer t.wg.Done() 71 72 timer := time.NewTimer(0) 73 cleared := false 74 reset := false 75 for !t.finished { // t.finished needs to be thread safe 76 if !reset && !timer.Stop() && !cleared { 77 <-timer.C 78 } 79 80 if cleared && t.shouldExecute { 81 t.lock.Unlock() 82 t.handler() 83 } else { 84 t.lock.Unlock() 85 } 86 87 cleared = false 88 reset = false 89 select { 90 case <-t.timeout: 91 t.lock.Lock() 92 if t.shouldExecute { 93 timer.Reset(t.duration) 94 } 95 reset = true 96 case <-timer.C: 97 t.lock.Lock() 98 cleared = true 99 } 100 } 101 } 102 103 func (t *Timer) reset() { 104 select { 105 case t.timeout <- struct{}{}: 106 default: 107 } 108 }