This code works as I expect it
import (
"fmt"
"time"
"github.com/benbjohnson/clock"
)
func main() {
mockClock := clock.NewMock()
timer := mockClock.Timer(time.Duration(2) * time.Second)
go func() {
<-timer.C
fmt.Println("Done")
}()
mockClock.Add(time.Duration(10) * time.Second)
time.Sleep(1)
}
It prints "Done" as I expect. Whereas this function does not
import (
"fmt"
"time"
"github.com/benbjohnson/clock"
)
func main() {
mockClock := clock.NewMock()
go func() {
timer := mockClock.Timer(time.Duration(2) * time.Second)
<-timer.C
fmt.Println("Done")
}()
mockClock.Add(time.Duration(10) * time.Second)
time.Sleep(1)
}
The only difference here is I'm declaring the timer outside the goroutine vs. inside it. The mockClock
Timer()
method has a pointer receiver and returns a pointer. I can't explain why the first one works and the second doesn't.
The package
benbjohnson/clock
provides mock time facilities. In particular their documentation states:So when you call
mockClock.Add
, it will sequentially execute the timers/tickers. The library also adds sequential 1 millisecond sleeps to artificially yield to other goroutines.When the timer/ticker is declared outside the goroutine, i.e. before calling
mockClock.Add
, by the timemockClock.Add
gets called the mock time does have something to execute. The library's internal sleeps are enough for the child goroutine to receive on the ticker and print "done", before the program exits.When the ticker is declared inside the goroutine, by the time
mockClock.Add
gets called, the mock time has no tickers to execute andAdd
essentially does nothing. The internal sleeps do give a chance to the child goroutine to run, but receiving on the ticker now just blocks; main then resumes and exits.You can also have a look at the ticker example that you can see in the repository's README:
This uses
runtime.Gosched()
to yield to the child goroutine before callingmock.Add
. The sequence of this program is basically:clock.NewMock()
count := 0
runtime.Gosched()
, yielding to the child goroutineticker := mock.Ticker(1 * time.Second)
<-ticker.C
(the mock clock hasn't moved forward yet)mock.Add
, which moves the clock forward and yields to the child goroutine againfor
loop with<-ticker.C
By the same logic, if you add a
runtime.Gosched()
to your second snippet, it will work as expected, just like the repository's example. Playground: https://go.dev/play/p/ZitEdtx9GdLHowever, do not rely on
runtime.Gosched()
in production code, possibly not even in test code, unless you're very sure about what you are doing.Finally, please remember that
time.Sleep(1)
sleeps for one nanosecond.