I have an idle timeout timer being select
ed on in a goroutine, if I see activity I want to cancel the timer.
I had a look at the documentation and I'm not positive I'm clear on what it says.
func (t *Timer) Stop() bool
Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired or been stopped. Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.To prevent a timer created with NewTimer from firing after a call to Stop, check the return value and drain the channel. For example, assuming the program has not received from t.C already:
if !t.Stop() { <-t.C }
This cannot be done concurrent to other receives from the Timer's channel.
I'm trying to understand when I have to drain the channel manually.
I'll list my understanding and if I'm wrong please correct me.
If Stop
returned false
this means either:
In my case getting a superfluous event from the timer is no big deal, does that inform what I should do here?
At each round of scheduling, it checks if the timers are ready to run, and if so, prepare them to run. Indeed, since the Go scheduler does not run any code itself, running the timer's callback will enqueue its goroutine to the local queue. Then, the goroutine will run when the scheduler picks it up in the queue.
Timeout (Timer) After waits for a specified duration and then sends the current time on the returned channel: select { case news := <-AFP: fmt. Println(news) case <-time.
Timers — These are used for one-off tasks. It represents a single event in the future. You tell the timer how long you want to wait, and it provides a channel that will be notified at that time. Tickers — Tickers are exceptionally helpful when you need to perform an action repeatedly at given time intervals.
The NewTimer() function in Go language is used to create a new Timer that will transmit the actual time on its channel at least after duration “d”. Moreover, this function is defined under the time package. Here, you need to import the “time” package in order to use these functions.
The reason that you might need to drain the channel is because of how goroutines are scheduled.
Problem
Imagine this case:
t.C
t.Stop()
is called.In this case there is a value on the channel t.C
, and t.Stop()
returns false because "the timer already expired" (i.e. when it sent the value on t.C
).
The reason that the docs say "this cannot be done concurrent to other receives" is because there's not guarantee of ordering between the if !t.Stop {
and the <-t.C
. The stop command could return false, entering the if body. And then another goroutine could be scheduled and read the value from t.C
that the body of the if statement was trying to drain. This would cause a datarace and result in blocking inside the if statement. (as you pointed out in your question!)
Solution
It depends on what the behaviour of the thing listening to the timer is.
If you are just in a simple select:
select {
case result <- doWork():
case <-t.C
}
Something like above. One of a few things could happen:
doWork
, sending the result, all good.doWork()
completing.t.Stop()
, but it's too
late because the value has been sent, causing the timeout, breaking
the select.As long as you are OK with case 4, you do not need to interact / drain the channel after calling Stop.
If you are not OK with case 4. You still cannot drain the t.C
channel because there's another goroutine listening to it. This could block in the if statement. Instead you must find another way of laying out the code, or ensuring that your goroutine in the select is not still listening on the channel.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With