Lawliet
Lawliet

Reputation: 167

What could happen if timer.Reset() is invoked on timer that has not expired or stopped

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

Answers (1)

Brits
Brits

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

Related Questions