-
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: document proper usage of Timer.Stop #14383
Comments
The go routine should get the timer as argument, because otherwise there will be a race. But even with that fixed the channel fires every few test runs. |
Aside: you do realize the iter package was a joke, right? I'd change your example to use @ulikunitz, I think the code looks fine. The goroutine closure captures the new |
This bit of code:
does not guarantee that there isn't an in-flight value on See extensive previous discussion on #11513 and https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVPoU. In the latter thread, @rsc suggests doing
I also previously filed #12721 to ask for an easier way to create a new, stopped timer. |
Indeed, looks like a dup of #12721. But let's keep this bug open as a documentation fix issue for Timer.Stop. An example might help too. |
Yes, it is a race.
Is not sufficient, as there's no way to tell if the fired event has already been received. Which in my use case it often has. This is necessary, as linked in the thread: http://play.golang.org/p/Ys9qqanqmU |
The race appears to be in the function timerproc() in runtime/time.go. The mutex timers.lock is unlocked for calling the runtime timer function, which sends the time into the channel. Therefore timer.Stop can return before the time value is written to the channel. Calling the runtime timer function with the mutex locked, ensures that timer.Stop cannot return before the channel is written too. I tested all.bash and also checked the test function from @anacrolix. I think this can be justified because there is no way for a user to set the runtime timer function. I even checked whether I can compile the time package outside of GOROOT; it didn't work. On the other hand I can't see how
should fail. At least on Linux AMD64 I was not able to trigger the modified test to fail, even after creating 1 million goroutines. It appears there are two ways to address this issue: A) Amend the documentation of time.Stop(), or I prefer B because it fixes the Test above. @bradfitz You are right, there is no race in the test code. |
I still don't think there's a race here. This is a documentation issue. See See also #11513. |
I should add: There is an argument for making
work, but it doesn't today, and we're certainly not going to do that for Go 1.7. Maybe early in Go 1.8 if someone wants to file a different bug. |
CL https://golang.org/cl/23575 mentions this issue. |
Just implemented a small fix, which can be used as a drop-in replacement. |
@m4ng0squ4sh FWIW, in the revised docs (https://beta.golang.org/pkg/time/#Timer.Stop) please note the phrase "assuming the program has not received from t.C already". That wrapper package is actually impossible to use correctly if the program has received from t.C already: the receive in the wrapper's Stop method will block waiting for a value that isn't coming. |
@rsc Thanks for pointing that out. That's why I included the following phrase:
I'll add some more documentation so it's clear for everybody. It might be possible to even solve that problem by adding another channel and a Mutex to the wrapper. However I have to try that first. Are there any other solutions? |
Note that most people would not consider:
to be calling t.Reset "concurrent to other receives". But the wrapper will hang in that case. The obvious solution is to use time.Timer directly and not write a wrapper. I suppose you could kick off a new goroutine to pass time events along a new channel and keep track of whether any have been seen, but then you're creating a much more heavyweight timer, all to avoid one or two lines of code. |
True story. Just have seen the updated doc (beta). Now it's clear. Thanks. Edit: Deleted the wrapper. |
@m4ng0squ4sh your Reset() is correct. |
@funny-falcon Thanks for having a look at it! |
@m4ng0squ4sh I don't think the Reset is correct. If it were correct I don't think this program would panic:
and if you fix that, be careful not to make this one hang:
I don't think you can paper over this. |
@rsc first, it panics with "time" package also. |
@rsc AfterFunc was added to the package to stay compatible to the standard lib timer implementation. As mentioned in the doc:
So the panic must be expected. Here is a simple ping example showing the general problem. I do believe that many gophers are not aware of the special Reset() conditions and that this is a common mistake. package main
import "time"
const (
keepaliveInterval = 2 * time.Millisecond
)
var (
resetC = make(chan struct{}, 1)
)
func main() {
go keepaliveLoop()
// Sample routine triggering the reset.
// Example: this could be due to incoming peer requests and
// a keepalive check should be reset to the max keepalive timeout.
for i := 0; i < 1000; i++ {
time.Sleep(time.Millisecond)
resetKeepalive()
}
}
func resetKeepalive() {
// Don't block if there is already a reset request.
select {
case resetC <- struct{}{}:
default:
}
}
func keepaliveLoop() {
t := time.NewTimer(keepaliveInterval)
for {
select {
case <-resetC:
time.Sleep(3 * time.Millisecond) // Simulate some reset work...
t.Reset(keepaliveInterval)
case <-t.C:
ping()
t.Reset(keepaliveInterval)
}
}
}
func ping() {
panic("ping must not be called in this example")
} |
@funny-falcon Yes, I know it panics with the Go time package. My point is that this package claims to provide a "fixed" Reset without these problems. It does not. |
@rsc, it claims to fix Reset only for channel variant. There were no mention of AfterFunc before your comment. |
Let's move this discussion off of a closed issue and onto golang-nuts. Thanks. |
After calling Timer.Stop(), and then clearing the channel, the channel can later be filled anyway. Presumably the task to send to the Timer channel has been put on a run queue, and isn't remove when Stop is called. Here's a test that demonstrates it:
Increase the value in iter.N if it doesn't trigger on your system initially. Note that it isn't even necessary to call Reset() to trigger this. You can remove that line. You can Stop() a timer, and then still receive a value from it, some time after you drain it.
The text was updated successfully, but these errors were encountered: