Documentation ¶
Overview ¶
Package timer helps with pitfalls using golang timers in loops.
For example, the following code leaks timers because time.After() does not garbage collect until the timer expires:
for { select { case somePayload, ok := <-someChan: if !ok { // channel is closed exit return } doSomething() checkSomethingLoop:: for _, foo := range someSlice { // It's possible we could block for a long time on writing to channel. If we cant write after Xms abort select { case writeChan <- "foo": doSomething case <-time.After(time.Duration(25) * time.Millisecond): break checkSomethingLoop } } } }
See: https://pkg.go.dev/time#After
The obvious solution is to use timer := time.NewTimer(time.Duration(25) * time.Millisecond) before the loop and reset it, but it turns out this has complications: https://pkg.go.dev/time#NewTimer https://github.com/golang/go/issues/11513
CockroachDB has a working solution to encapsulate the complexity. This code is copied from CockroachDB when under Apache license in 2019: https://github.com/cockroachdb/cockroach/blob/4d82429ba71d1d2a868ebff38f6d8f7ce3595d21/pkg/util/timeutil/timer.go
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Timer ¶
type Timer struct { // C is a local "copy" of timer.C that can be used in a select case before // the timer has been initialized (via Reset). C <-chan time.Time Read bool // contains filtered or unexported fields }
The Timer type represents a single event. When the Timer expires, the current time will be sent on Timer.C.
This timer implementation is an abstraction around the standard library's time.Timer that provides a temporary workaround for the issue described in https://github.com/golang/go/issues/14038. As such, this timer should only be used when Reset is planned to be called continually in a loop. For this Reset pattern to work, Timer.Read must be set to true whenever a timestamp is read from the Timer.C channel. If Timer.Read is not set to true when the channel is read from, the next call to Timer.Reset will deadlock. This pattern looks something like:
var tmr timer.Timer defer tmr.Stop() for { tmr.Reset(wait) select { case <-tmr.C: tmr.Read = true ... } }
Note that unlike the standard library's Timer type, this Timer will not begin counting down until Reset is called for the first time, as there is no constructor function.
func (*Timer) Reset ¶
Reset changes the timer to expire after duration d and returns the new value of the timer. This method includes the fix proposed in https://github.com/golang/go/issues/11513#issuecomment-157062583, but requires users of Timer to set Timer.Read to true whenever they successfully read from the Timer's channel.
func (*Timer) Stop ¶
Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired, been stopped previously, or had never been initialized with a call to Timer.Reset. Stop does not close the channel, to prevent a read from succeeding incorrectly. Note that a Timer must never be used again after calls to Stop as the timer object will be put into an object pool for reuse.