Skip to content

Commit 75ce472

Browse files
Merge pull request #29 from tanium/fix-race
Fix the NewTicker race and add regression tests.
2 parents 123636d + 4104c99 commit 75ce472

File tree

3 files changed

+66
-29
lines changed

3 files changed

+66
-29
lines changed

clockwork.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func (fc *fakeClock) NewTicker(d time.Duration) Ticker {
152152
clock: fc,
153153
period: d,
154154
}
155-
go ft.tick()
155+
ft.runTickThread()
156156
return ft
157157
}
158158

ticker.go

+32-26
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,39 @@ func (ft *fakeTicker) Stop() {
3434
ft.stop <- true
3535
}
3636

37-
// tick sends the tick time to the ticker channel after every period.
38-
// Tick events are discarded if the underlying ticker channel does
39-
// not have enough capacity.
40-
func (ft *fakeTicker) tick() {
41-
tick := ft.clock.Now()
42-
for {
43-
tick = tick.Add(ft.period)
44-
remaining := tick.Sub(ft.clock.Now())
45-
if remaining <= 0 {
46-
// The tick should have already happened. This can happen when
47-
// Advance() is called on the fake clock with a duration larger
48-
// than this ticker's period.
37+
// runTickThread initializes a background goroutine to send the tick time to the ticker channel
38+
// after every period. Tick events are discarded if the underlying ticker channel does not have
39+
// enough capacity.
40+
func (ft *fakeTicker) runTickThread() {
41+
nextTick := ft.clock.Now().Add(ft.period)
42+
next := ft.clock.After(ft.period)
43+
go func() {
44+
for {
4945
select {
50-
case ft.c <- tick:
51-
default:
46+
case <-ft.stop:
47+
return
48+
case <-next:
49+
// We send the time that the tick was supposed to occur at.
50+
tick := nextTick
51+
// Before sending the tick, we'll compute the next tick time and star the clock.After call.
52+
now := ft.clock.Now()
53+
// First, figure out how many periods there have been between "now" and the time we were
54+
// supposed to have trigged, then advance over all of those.
55+
skipTicks := (now.Sub(tick) + ft.period - 1) / ft.period
56+
nextTick = nextTick.Add(skipTicks * ft.period)
57+
// Now, keep advancing until we are past now. This should happen at most once.
58+
for !nextTick.After(now) {
59+
nextTick = nextTick.Add(ft.period)
60+
}
61+
// Figure out how long between now and the next scheduled tick, then wait that long.
62+
remaining := nextTick.Sub(now)
63+
next = ft.clock.After(remaining)
64+
// Finally, we can actually send the tick.
65+
select {
66+
case ft.c <- tick:
67+
default:
68+
}
5269
}
53-
continue
5470
}
55-
56-
select {
57-
case <-ft.stop:
58-
return
59-
case <-ft.clock.After(remaining):
60-
select {
61-
case ft.c <- tick:
62-
default:
63-
}
64-
}
65-
}
71+
}()
6672
}

ticker_test.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package clockwork
22

33
import (
44
"testing"
5+
"time"
56
)
67

78
func TestFakeTickerStop(t *testing.T) {
@@ -38,7 +39,7 @@ func TestFakeTickerTick(t *testing.T) {
3839
if tick != first {
3940
t.Errorf("wrong tick time, got: %v, want: %v", tick, first)
4041
}
41-
default:
42+
case <-time.After(time.Millisecond):
4243
t.Errorf("expected tick!")
4344
}
4445

@@ -51,8 +52,38 @@ func TestFakeTickerTick(t *testing.T) {
5152
if tick != second {
5253
t.Errorf("wrong tick time, got: %v, want: %v", tick, second)
5354
}
54-
default:
55+
case <-time.After(time.Millisecond):
5556
t.Errorf("expected tick!")
5657
}
5758
ft.Stop()
5859
}
60+
61+
func TestFakeTicker_Race(t *testing.T) {
62+
fc := NewFakeClock()
63+
64+
tickTime := 1 * time.Millisecond
65+
ticker := fc.NewTicker(tickTime)
66+
defer ticker.Stop()
67+
68+
fc.Advance(tickTime)
69+
70+
timeout := time.NewTimer(500 * time.Millisecond)
71+
defer timeout.Stop()
72+
73+
select {
74+
case <-ticker.Chan():
75+
// Pass
76+
case <-timeout.C:
77+
t.Fatalf("Ticker didn't detect the clock advance!")
78+
}
79+
}
80+
81+
func TestFakeTicker_Race2(t *testing.T) {
82+
fc := NewFakeClock()
83+
ft := fc.NewTicker(5 * time.Second)
84+
for i := 0; i < 100; i++ {
85+
fc.Advance(5 * time.Second)
86+
<-ft.Chan()
87+
}
88+
ft.Stop()
89+
}

0 commit comments

Comments
 (0)