Skip to content

Commit df58822

Browse files
Merge pull request #38 from rosstimothy/master
Add Timers
2 parents 601f21d + a2eb557 commit df58822

File tree

3 files changed

+259
-1
lines changed

3 files changed

+259
-1
lines changed

clockwork.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Clock interface {
1313
Now() time.Time
1414
Since(t time.Time) time.Duration
1515
NewTicker(d time.Duration) Ticker
16+
NewTimer(d time.Duration) Timer
1617
}
1718

1819
// FakeClock provides an interface for a clock which can be
@@ -70,6 +71,10 @@ func (rc *realClock) NewTicker(d time.Duration) Ticker {
7071
return &realTicker{time.NewTicker(d)}
7172
}
7273

74+
func (rc *realClock) NewTimer(d time.Duration) Timer {
75+
return &realTimer{time.NewTimer(d)}
76+
}
77+
7378
type fakeClock struct {
7479
sleepers []*sleeper
7580
blockers []*blocker
@@ -132,7 +137,7 @@ func (fc *fakeClock) Sleep(d time.Duration) {
132137
<-fc.After(d)
133138
}
134139

135-
// Time returns the current time of the fakeClock
140+
// Now returns the current time of the fakeClock
136141
func (fc *fakeClock) Now() time.Time {
137142
fc.l.RLock()
138143
t := fc.time
@@ -145,6 +150,8 @@ func (fc *fakeClock) Since(t time.Time) time.Duration {
145150
return fc.Now().Sub(t)
146151
}
147152

153+
// NewTicker returns a ticker that will expire only after calls to fakeClock
154+
// Advance have moved the clock passed the given duration
148155
func (fc *fakeClock) NewTicker(d time.Duration) Ticker {
149156
ft := &fakeTicker{
150157
c: make(chan time.Time, 1),
@@ -156,6 +163,25 @@ func (fc *fakeClock) NewTicker(d time.Duration) Ticker {
156163
return ft
157164
}
158165

166+
// NewTimer returns a timer that will fire only after calls to fakeClock
167+
// Advance have moved the clock passed the given duration
168+
func (fc *fakeClock) NewTimer(d time.Duration) Timer {
169+
stopped := uint32(0)
170+
if d <= 0 {
171+
stopped = 1
172+
}
173+
ft := &fakeTimer{
174+
c: make(chan time.Time, 1),
175+
stop: make(chan struct{}, 1),
176+
reset: make(chan reset, 1),
177+
clock: fc,
178+
stopped: stopped,
179+
}
180+
181+
ft.run(d)
182+
return ft
183+
}
184+
159185
// Advance advances fakeClock to a new point in time, ensuring channels from any
160186
// previous invocations of After are notified appropriately before returning
161187
func (fc *fakeClock) Advance(d time.Duration) {

timer.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package clockwork
2+
3+
import (
4+
"sync/atomic"
5+
"time"
6+
)
7+
8+
// Timer provides an interface which can be used instead of directly
9+
// using the timer within the time module. The real-time timer t
10+
// provides events through t.C which becomes now t.Chan() to make
11+
// this channel requirement definable in this interface.
12+
type Timer interface {
13+
Chan() <-chan time.Time
14+
Reset(d time.Duration) bool
15+
Stop() bool
16+
}
17+
18+
type realTimer struct {
19+
*time.Timer
20+
}
21+
22+
func (r realTimer) Chan() <-chan time.Time {
23+
return r.C
24+
}
25+
26+
type fakeTimer struct {
27+
c chan time.Time
28+
clock FakeClock
29+
stop chan struct{}
30+
reset chan reset
31+
stopped uint32
32+
}
33+
34+
func (f *fakeTimer) Chan() <-chan time.Time {
35+
return f.c
36+
}
37+
38+
func (f *fakeTimer) Reset(d time.Duration) bool {
39+
stopped := f.Stop()
40+
41+
f.reset <- reset{t: f.clock.Now().Add(d), next: f.clock.After(d)}
42+
if d > 0 {
43+
atomic.StoreUint32(&f.stopped, 0)
44+
}
45+
46+
return stopped
47+
}
48+
49+
func (f *fakeTimer) Stop() bool {
50+
if atomic.CompareAndSwapUint32(&f.stopped, 0, 1) {
51+
f.stop <- struct{}{}
52+
return true
53+
}
54+
return false
55+
}
56+
57+
type reset struct {
58+
t time.Time
59+
next <-chan time.Time
60+
}
61+
62+
// run initializes a background goroutine to send the timer event to the timer channel
63+
// after the period. Events are discarded if the underlying ticker channel does not have
64+
// enough capacity.
65+
func (f *fakeTimer) run(initialDuration time.Duration) {
66+
nextTick := f.clock.Now().Add(initialDuration)
67+
next := f.clock.After(initialDuration)
68+
69+
waitForReset := func() (time.Time, <-chan time.Time) {
70+
for {
71+
select {
72+
case <-f.stop:
73+
continue
74+
case r := <-f.reset:
75+
return r.t, r.next
76+
}
77+
}
78+
}
79+
80+
go func() {
81+
for {
82+
select {
83+
case <-f.stop:
84+
case <-next:
85+
atomic.StoreUint32(&f.stopped, 1)
86+
select {
87+
case f.c <- nextTick:
88+
default:
89+
}
90+
91+
next = nil
92+
}
93+
94+
nextTick, next = waitForReset()
95+
}
96+
}()
97+
}

timer_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package clockwork
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestFakeClockTimerStop(t *testing.T) {
9+
fc := &fakeClock{}
10+
11+
ft := fc.NewTimer(1)
12+
ft.Stop()
13+
select {
14+
case <-ft.Chan():
15+
t.Errorf("received unexpected tick!")
16+
default:
17+
}
18+
}
19+
20+
func TestFakeClockTimers(t *testing.T) {
21+
fc := &fakeClock{}
22+
23+
zero := fc.NewTimer(0)
24+
25+
if zero.Stop() {
26+
t.Errorf("zero timer could be stopped")
27+
}
28+
29+
timeout := time.NewTimer(500 * time.Millisecond)
30+
defer timeout.Stop()
31+
32+
select {
33+
case <-zero.Chan():
34+
case <-timeout.C:
35+
t.Errorf("zero timer didn't emit time")
36+
}
37+
38+
one := fc.NewTimer(1)
39+
40+
select {
41+
case <-one.Chan():
42+
t.Errorf("non-zero timer did emit time")
43+
default:
44+
}
45+
if !one.Stop() {
46+
t.Errorf("non-zero timer couldn't be stopped")
47+
}
48+
49+
fc.Advance(5)
50+
51+
select {
52+
case <-one.Chan():
53+
t.Errorf("stopped timer did emit time")
54+
default:
55+
}
56+
57+
if one.Reset(1) {
58+
t.Errorf("resetting stopped timer didn't return false")
59+
}
60+
if !one.Reset(1) {
61+
t.Errorf("resetting active timer didn't return true")
62+
}
63+
64+
fc.Advance(1)
65+
66+
select {
67+
case <-time.After(500 * time.Millisecond):
68+
}
69+
70+
if one.Stop() {
71+
t.Errorf("triggered timer could be stopped")
72+
}
73+
74+
timeout2 := time.NewTimer(500 * time.Millisecond)
75+
defer timeout2.Stop()
76+
77+
select {
78+
case <-one.Chan():
79+
case <-timeout2.C:
80+
t.Errorf("triggered timer didn't emit time")
81+
}
82+
83+
fc.Advance(1)
84+
85+
select {
86+
case <-one.Chan():
87+
t.Errorf("triggered timer emitted time more than once")
88+
default:
89+
}
90+
91+
one.Reset(0)
92+
93+
if one.Stop() {
94+
t.Errorf("reset to zero timer could be stopped")
95+
}
96+
97+
timeout3 := time.NewTimer(500 * time.Millisecond)
98+
defer timeout3.Stop()
99+
100+
select {
101+
case <-one.Chan():
102+
case <-timeout3.C:
103+
t.Errorf("reset to zero timer didn't emit time")
104+
}
105+
}
106+
107+
func TestFakeClockTimer_Race(t *testing.T) {
108+
fc := NewFakeClock()
109+
110+
timer := fc.NewTimer(1 * time.Millisecond)
111+
defer timer.Stop()
112+
113+
fc.Advance(1 * time.Millisecond)
114+
115+
timeout := time.NewTimer(500 * time.Millisecond)
116+
defer timeout.Stop()
117+
118+
select {
119+
case <-timer.Chan():
120+
// Pass
121+
case <-timeout.C:
122+
t.Fatalf("Timer didn't detect the clock advance!")
123+
}
124+
}
125+
126+
func TestFakeClockTimer_Race2(t *testing.T) {
127+
fc := NewFakeClock()
128+
timer := fc.NewTimer(5 * time.Second)
129+
for i := 0; i < 100; i++ {
130+
fc.Advance(5 * time.Second)
131+
<-timer.Chan()
132+
timer.Reset(5 * time.Second)
133+
}
134+
timer.Stop()
135+
}

0 commit comments

Comments
 (0)