Here is my test code:
package main
import (
"runtime"
"testing"
"time"
)
func testTimerRoutine(expire bool) bool {
dur := 10 * time.Millisecond
timer := time.NewTimer(dur)
leak := true
go func() {
defer func() {
println("timer routine quit")
leak = false
}()
<-timer.C
println("timer expired")
}()
if expire {
time.Sleep(2 * dur)
}
if !timer.Stop() {
println("timer stop false")
<-timer.C
} else {
println("timer stop true")
}
runtime.GC()
time.Sleep(10 * dur)
return leak
}
func TestTimerRoutineDeadlock(t *testing.T) {
leak := testTimerRoutine(true)
if leak {
t.Errorf("routine leak")
}
}
func TestTimerRoutineLeak(t *testing.T) {
leak := testTimerRoutine(false)
if leak {
t.Errorf("routine leak")
}
}
I meant to think that without other channels, just using time.Timer.C
.
I now start a go routine dedicated to reading timer.C
in response to a timer timeout after the timer is started.
But I found two problems with this approach:
Stop()
returns false, there will be a deadlock at this point to drain timer.C
as required by the API.I wonder if there is a way, without using an additional Channel, to achieve the functionality I require to stop the timer early in response to a timer timeout.
Here's a quote from the documentation of timer:
This (Stop) cannot be done concurrent to other receives from the Timer's channel or other calls to the Timer's Stop method.
Yet, this is exactly what you are doing. You have a goroutine receiving from a timer channel concurrently with the Stop call.
In general, if you need to stop a timer because of some sort of an event, send a request to the listening goroutine to stop the timer.
You don't have to drain the channel as it is a channel with capacity 1, so if nothing is listening from it, it will be garbage collected.