github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/timeutil/timer.go (about) 1 // Copyright 2016 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package timeutil 12 13 import ( 14 "sync" 15 "time" 16 ) 17 18 var timerPool = sync.Pool{ 19 New: func() interface{} { 20 return &Timer{} 21 }, 22 } 23 var timeTimerPool sync.Pool 24 25 // The Timer type represents a single event. When the Timer expires, 26 // the current time will be sent on Timer.C. 27 // 28 // This timer implementation is an abstraction around the standard 29 // library's time.Timer that provides a temporary workaround for the 30 // issue described in https://github.com/golang/go/issues/14038. As 31 // such, this timer should only be used when Reset is planned to 32 // be called continually in a loop. For this Reset pattern to work, 33 // Timer.Read must be set to true whenever a timestamp is read from 34 // the Timer.C channel. If Timer.Read is not set to true when the 35 // channel is read from, the next call to Timer.Reset will deadlock. 36 // This pattern looks something like: 37 // 38 // var timer timeutil.Timer 39 // defer timer.Stop() 40 // for { 41 // timer.Reset(wait) 42 // select { 43 // case <-timer.C: 44 // timer.Read = true 45 // ... 46 // } 47 // } 48 // 49 // Note that unlike the standard library's Timer type, this Timer will 50 // not begin counting down until Reset is called for the first time, as 51 // there is no constructor function. 52 type Timer struct { 53 timer *time.Timer 54 // C is a local "copy" of timer.C that can be used in a select case before 55 // the timer has been initialized (via Reset). 56 C <-chan time.Time 57 Read bool 58 } 59 60 // NewTimer allocates a new timer. 61 func NewTimer() *Timer { 62 return timerPool.Get().(*Timer) 63 } 64 65 // Reset changes the timer to expire after duration d and returns 66 // the new value of the timer. This method includes the fix proposed 67 // in https://github.com/golang/go/issues/11513#issuecomment-157062583, 68 // but requires users of Timer to set Timer.Read to true whenever 69 // they successfully read from the Timer's channel. 70 func (t *Timer) Reset(d time.Duration) { 71 if t.timer == nil { 72 switch timer := timeTimerPool.Get(); timer { 73 case nil: 74 t.timer = time.NewTimer(d) 75 default: 76 t.timer = timer.(*time.Timer) 77 t.timer.Reset(d) 78 } 79 t.C = t.timer.C 80 return 81 } 82 if !t.timer.Stop() && !t.Read { 83 <-t.C 84 } 85 t.timer.Reset(d) 86 t.Read = false 87 } 88 89 // Stop prevents the Timer from firing. It returns true if the call stops 90 // the timer, false if the timer has already expired, been stopped previously, 91 // or had never been initialized with a call to Timer.Reset. Stop does not 92 // close the channel, to prevent a read from succeeding incorrectly. 93 // Note that a Timer must never be used again after calls to Stop as the timer 94 // object will be put into an object pool for reuse. 95 func (t *Timer) Stop() bool { 96 var res bool 97 if t.timer != nil { 98 res = t.timer.Stop() 99 if res { 100 // Only place the timer back in the pool if we successfully stopped 101 // it. Otherwise, we'd have to read from the channel if !t.Read. 102 timeTimerPool.Put(t.timer) 103 } 104 } 105 *t = Timer{} 106 timerPool.Put(t) 107 return res 108 }