Reputation: 167
Accoding to doc... For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.
If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly. If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained.
Can anyone give an example of what kind of issue/errors could arise if this is not done.
Upvotes: -2
Views: 125
Reputation: 18380
I am trying to identify id this situation would cause an runtime error
It won't. The code runs when the timer fires might raise such an error, but calling Reset
will not (unless there is an error in your logic and timer
is nil
).
Can anyone give an example of what kind of issue/errors could arise if this is not done.
Here is an example playground
func main() {
timer1 := time.NewTimer(time.Millisecond)
timer2 := time.NewTimer(5 * time.Millisecond)
for i := 0; i < 10; i++ {
select {
case <-timer1.C:
fmt.Println("timer1")
// Lets assume this does something that takes a while.
time.Sleep(10 * time.Millisecond)
// The result of the action is that we want to reset both timers (timer2 should not fire)
timer1.Reset(time.Millisecond)
timer2.Reset(5 * time.Millisecond)
case <-timer2.C:
fmt.Println("timer2")
timer1.Reset(time.Millisecond)
timer2.Reset(5 * time.Millisecond)
}
}
}
Scanning through this code you might think "when timer 1 fires both timers are reset so timer2 will never fire and the output will be":
timer1
timer1
timer1
...
But the reality is that the output is:
timer1
timer2
timer1
timer2
...
The reason for this is that by the time timer2.Reset(5 * time.Millisecond)
is called timer2
has already fired, so on the next loop case <-timer2.C
will be selected. The issue is that unless you know that a timer has been stopped (and the channel drained, if necessary) the result of calling Reset
can be unpredictable. See this issue for a discussion around a few related issues.
This is a fairly contrived example so it's quite easy to spot the problem but in real code it's not always obvious (the documentation makes this statement for a reason, trust it!). By stopping, draining and then resetting the timer you end up with a known state. Modifying the example as follows delivers the output I suggested might be expected (playground):
func main() {
timer1 := time.NewTimer(time.Millisecond)
timer2 := time.NewTimer(5 * time.Millisecond)
for i := 0; i < 10; i++ {
select {
case <-timer1.C:
fmt.Println("timer1")
// Lets assume this does something that takes a while.
time.Sleep(10 * time.Millisecond)
// The result of the action is that we want to reset both timers (timer2 should not fire)
timer1.Reset(time.Millisecond) // We know timer1 has fired so don't need to stop it
resetActiveTimer(timer2, 5*time.Millisecond)
case <-timer2.C:
fmt.Println("timer2")
resetActiveTimer(timer1, time.Millisecond)
timer2.Reset(5 * time.Millisecond) // We know timer2 has fired so don't need to stop it
}
}
}
func resetActiveTimer(t *time.Timer, d time.Duration) {
if !t.Stop() {
<-t.C
}
t.Reset(d)
}
Upvotes: 2