-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
time: Timer.C can still trigger even after Timer.Reset is called #11513
Comments
If the timer legitimately expired, wouldn't I possibly want that value? Another goroutine could be checking t.C (maybe even counting on the fact that it's buffered), and I'd rather that calling Reset didn't throw that value away. |
I don't think that I or many others want that value because firstly, in this case, only a single Timer instance is used in a single goroutine, not being called reset and checked t.C concurrently as your usage intention. Secondly, although it legitimately expired and a time value is already in t.C, but it had been called Reset before any value received from t.C therefore the old time value from t.C is not what user expected. If you keep the old value, you could possibly be interrupted even before you give any chance to allow an operation to proceed. |
Even using select statement to mitigate the problem is not enough as the following test (also available on Playground) consistently failed on any machine that has 2 or more real CPU cores: // testTimer.go
package main
import (
"runtime"
"time"
)
func retimer(t *time.Timer, d time.Duration) {
if !t.Stop() {
select {
case <-t.C:
default:
}
}
t.Reset(d)
}
func runTimer() {
tmr := time.NewTimer(0)
retimer(tmr, time.Minute)
select {
case <-tmr.C:
panic("unexpected firing of Timer")
default:
}
}
func main() {
runtime.GOMAXPROCS(2)
for {
runTimer()
}
} |
CL https://golang.org/cl/11960 mentions this issue. |
@rogpeppe Are you sure your fix is guaranteed to work? I can reproduce @enormouspenguin's result (no need for the GOMAXPROCS setting in 1.5). You gave this workaround in your original post: t.Stop()
select {
case <-t.C:
default:
}
t.Reset(x) However, this is subject to failure because of a race condition; consider the following sequence of events:
See http://play.golang.org/p/80kzKDerpZ for a demonstration. Edit: If the timer was previously set but the channel wasn't received from and you want to reliably reset it, do the following: if !timer.Stop() {
<-timer.C
}
timer.Reset(x) Please ignore the code below.
L:
for !timer.Stop() {
select {
case <-timer.C:
break L
default:
}
runtime.Gosched() // not needed, but prevents some extra spinning
}
timer.Reset(x) |
I feel that the
While technically true, "prevents the Timer from firing" isn't the same as "prevents future channel reads from succeeding", which is what is observable to the user. It seems to me that the intention of Stop is subtly incompatible with the semantics of buffered channels. |
I agree with @jbardin. It's not the library's job to throw away values already sent on the channel. This program changes behavior with the suggested CL:
|
This is an unfortunate resolution. A very common use case for Timer.Reset is Consider the following loop:
Here the time.After is modelling some external event that fires, and the The challenge here is to find a way of resetting the timer so that the None of the solutions presented in this thread (other than changing Here is some code that demonstrates this: If we can't fix timer.Reset, perhaps we could add a new method that |
I'm sorry, but I didn't really understand the playground code. I think I do understand what you're saying about the initial snippet, though. Here is a variant on it that prints "short sleep" on each iteration that gets a stale value on t.C:
And here is the same program corrected, by using the return value of t.Reset and tracking whether the receive from t.C has happened already:
|
@enormouspenguin A closed issue is not the right place to have this kind of conversation. It should happen on the golang-dev mailing list. Thanks. |
I deleted my previous inappropriate comment. Thanks for reminding me, @ianlancetaylor. |
Discussion did continue: https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVPoU |
Yesterday I landed go.dev/cl/568341, which fixes this bug. |
A common idiom is to keep a single timer and extend its
use by calling Timer.Reset.
From a naive reading of the documentation, these two
lines are equivalent except for saving some garbage:
Unfortunately t.C is buffered, so if the timer has just expired,
the newly reset timer can actually trigger immediately.
The safe way to do it might be someting like:
but this is cumbersome. Perhaps we could change Reset to do this
as a matter of course.
The text was updated successfully, but these errors were encountered: