github.com/haraldrudell/parl@v0.4.176/ptime/thread-safe-timer.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package ptime 7 8 import ( 9 "sync" 10 "time" 11 ) 12 13 const ( 14 // if new-function is invoked with zero default duration, 15 // default default-duration is 1 second 16 defaultDefaultDuration = time.Second 17 ) 18 19 // ThreadSafeTimer is a timer that can be Reset by multiple threads. Improvements: 20 // - Stop and Reset are thread-safe 21 // - Reset includes Stop in a fail-safe sequence and can be invoked at any time by any thread 22 // - NewThreadSafeTimer has a defaultDuration of 1 second if argument is missing, zero or negative 23 // - Reset also uses defaultDuration if argument is zero or negative 24 // - fields and method signatures are identical to [time.Timer] 25 // - ThreadSafeTimer may be copied or stored in slices or maps 26 // - — 27 // - cost: 1 sync.Mutex, 1 int64, 2 pointers, 2 allocations 28 // - [time.Timer] public methods and fields: C Reset Stop 29 // - time.Timer Reset must be in an uninterrupted Stop-drain-Reset sequence or memory leaks result 30 type ThreadSafeTimer struct { 31 // the default duration for Reset method 32 defaultDuration time.Duration 33 // the timer initially stopped 34 *time.Timer 35 // resetLock makes Stop, drain and Reset sequence atomic 36 resetLock *sync.Mutex 37 } 38 39 // NewThreadSafeTimer returns a running timer with thread-safe Reset 40 // - default defaultDuration is 1 second for defaultDuration missing, zero or negative 41 // - defaultDuration is the default for Reset when Reset argument is zero or negative 42 // - Reset can be invoked at any time without any precautions. 43 // - — [time.Timer.Reset] has many conditions to avoid memory leaks 44 // - Stop and Reset methods are thread-safe 45 // - a timer must either expire or have Stop invoked to release resources 46 // - if timer was created in same thread or obtained via synchronize before, 47 // read of field C is thread-safe 48 func NewThreadSafeTimer(defaultDuration ...time.Duration) (timer *ThreadSafeTimer) { 49 50 // determine default duration 51 var d time.Duration 52 if len(defaultDuration) > 0 { 53 d = defaultDuration[0] 54 } 55 if d <= 0 { 56 d = defaultDefaultDuration // 1 second 57 } 58 59 return &ThreadSafeTimer{ 60 defaultDuration: d, 61 Timer: time.NewTimer(d), 62 resetLock: &sync.Mutex{}, 63 } 64 } 65 66 // Reset is thread-safe timer reset 67 // - has default duration for duration == 0 68 // - works with concurrent channel read 69 // - works with concurrent timer.Stop 70 // - — 71 // - thread-safety is obtained by making the Stop-drain-Reset sequence atomic 72 // - unsynchronized Reset will cause memory leaks 73 func (t *ThreadSafeTimer) Reset(duration time.Duration) { 74 if duration <= 0 { 75 duration = t.defaultDuration 76 } 77 t.resetLock.Lock() 78 defer t.resetLock.Unlock() 79 80 // Reset should be invoked only on: 81 // - stopped or expired timers 82 // - with drained channels 83 t.Timer.Stop() 84 select { 85 case <-t.Timer.C: 86 default: 87 } 88 t.Timer.Reset(duration) 89 } 90 91 // Stop prevents the Timer from firing 92 func (t *ThreadSafeTimer) Stop() (wasRunning bool) { 93 t.resetLock.Lock() 94 defer t.resetLock.Unlock() 95 96 return t.Timer.Stop() 97 }