Go Timer implementation with a fixed Reset behavior
This is a lightweight timer implementation which is a drop-in replacement for
Go's Timer. Reset behaves as one would expect and drains the timer.C channel automatically.
The core design of this package is similar to the original runtime timer implementation.
These two lines are equivalent except for saving some garbage:
t.Reset(x)
t := timer.NewTimer(x)
See issues:
Quote from the Timer Go doc reference:
Reset changes the timer to expire after duration d.
It returns true if the timer had been active, false if the timer had
expired or been stopped.
To reuse an active timer, always call its Stop method first and—if it had
expired—drain the value from its channel. For example: [...]
This should not be done concurrent to other receives from the Timer's channel.
Note that it is not possible to use Reset's return value correctly, as there
is a race condition between draining the channel and the new timer expiring.
Reset should always be used in concert with Stop, as described above.
The return value exists to preserve compatibility with existing programs.
Broken behavior sample
Sample 1
package main
import (
"log"
"time"
)
func main() {
start := time.Now()
timer := time.NewTimer(1 * time.Second)
time.Sleep(2 * time.Second)
timer.Reset(1 * time.Second)
<-timer.C
if int(time.Since(start).Seconds()) != 3 {
log.Fatalf("took ~%v seconds, should be ~3 seconds\n", int(time.Since(start).Seconds()))
}
}
Sample 2
package main
import "time"
const (
keepaliveInterval = 2 * time.Millisecond
)
var (
resetC = make(chan struct{}, 1)
)
func main() {
go keepaliveLoop()
for i := 0; i < 1000; i++ {
time.Sleep(time.Millisecond)
resetKeepalive()
}
}
func resetKeepalive() {
select {
case resetC <- struct{}{}:
default:
}
}
func keepaliveLoop() {
t := time.NewTimer(keepaliveInterval)
for {
select {
case <-resetC:
time.Sleep(3 * time.Millisecond)
t.Reset(keepaliveInterval)
case <-t.C:
ping()
t.Reset(keepaliveInterval)
}
}
}
func ping() {
panic("ping must not be called in this example")
}