-
Notifications
You must be signed in to change notification settings - Fork 58
/
clockwork.go
221 lines (194 loc) · 5.49 KB
/
clockwork.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package clockwork
import (
"sync"
"time"
)
// Clock provides an interface that packages can use instead of directly
// using the time module, so that chronology-related behavior can be tested
type Clock interface {
After(d time.Duration) <-chan time.Time
Sleep(d time.Duration)
Now() time.Time
Since(t time.Time) time.Duration
NewTicker(d time.Duration) Ticker
NewTimer(d time.Duration) Timer
}
// FakeClock provides an interface for a clock which can be
// manually advanced through time
type FakeClock interface {
Clock
// Advance advances the FakeClock to a new point in time, ensuring any existing
// sleepers are notified appropriately before returning
Advance(d time.Duration)
// BlockUntil will block until the FakeClock has the given number of
// sleepers (callers of Sleep or After)
BlockUntil(n int)
}
// NewRealClock returns a Clock which simply delegates calls to the actual time
// package; it should be used by packages in production.
func NewRealClock() Clock {
return &realClock{}
}
// NewFakeClock returns a FakeClock implementation which can be
// manually advanced through time for testing. The initial time of the
// FakeClock will be an arbitrary non-zero time.
func NewFakeClock() FakeClock {
// use a fixture that does not fulfill Time.IsZero()
return NewFakeClockAt(time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC))
}
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
func NewFakeClockAt(t time.Time) FakeClock {
return &fakeClock{
time: t,
}
}
type realClock struct{}
func (rc *realClock) After(d time.Duration) <-chan time.Time {
return time.After(d)
}
func (rc *realClock) Sleep(d time.Duration) {
time.Sleep(d)
}
func (rc *realClock) Now() time.Time {
return time.Now()
}
func (rc *realClock) Since(t time.Time) time.Duration {
return rc.Now().Sub(t)
}
func (rc *realClock) NewTicker(d time.Duration) Ticker {
return &realTicker{time.NewTicker(d)}
}
func (rc *realClock) NewTimer(d time.Duration) Timer {
return &realTimer{time.NewTimer(d)}
}
type fakeClock struct {
sleepers []*sleeper
blockers []*blocker
time time.Time
l sync.RWMutex
}
// sleeper represents a caller of After or Sleep
type sleeper struct {
until time.Time
done chan time.Time
}
// blocker represents a caller of BlockUntil
type blocker struct {
count int
ch chan struct{}
}
// After mimics time.After; it waits for the given duration to elapse on the
// fakeClock, then sends the current time on the returned channel.
func (fc *fakeClock) After(d time.Duration) <-chan time.Time {
fc.l.Lock()
defer fc.l.Unlock()
now := fc.time
done := make(chan time.Time, 1)
if d.Nanoseconds() <= 0 {
// special case - trigger immediately
done <- now
} else {
// otherwise, add to the set of sleepers
s := &sleeper{
until: now.Add(d),
done: done,
}
fc.sleepers = append(fc.sleepers, s)
// and notify any blockers
fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
}
return done
}
// notifyBlockers notifies all the blockers waiting until the at least the given
// number of sleepers are waiting on the fakeClock. It returns an updated slice
// of blockers (i.e. those still waiting)
func notifyBlockers(blockers []*blocker, count int) (newBlockers []*blocker) {
for _, b := range blockers {
if b.count <= count {
close(b.ch)
} else {
newBlockers = append(newBlockers, b)
}
}
return
}
// Sleep blocks until the given duration has passed on the fakeClock
func (fc *fakeClock) Sleep(d time.Duration) {
<-fc.After(d)
}
// Now returns the current time of the fakeClock
func (fc *fakeClock) Now() time.Time {
fc.l.RLock()
t := fc.time
fc.l.RUnlock()
return t
}
// Since returns the duration that has passed since the given time on the fakeClock
func (fc *fakeClock) Since(t time.Time) time.Duration {
return fc.Now().Sub(t)
}
// NewTicker returns a ticker that will expire only after calls to fakeClock
// Advance have moved the clock passed the given duration
func (fc *fakeClock) NewTicker(d time.Duration) Ticker {
ft := &fakeTicker{
c: make(chan time.Time, 1),
stop: make(chan bool, 1),
clock: fc,
period: d,
}
ft.runTickThread()
return ft
}
// NewTimer returns a timer that will fire only after calls to fakeClock
// Advance have moved the clock passed the given duration
func (fc *fakeClock) NewTimer(d time.Duration) Timer {
stopped := uint32(0)
if d <= 0 {
stopped = 1
}
ft := &fakeTimer{
c: make(chan time.Time, 1),
stop: make(chan struct{}, 1),
reset: make(chan reset, 1),
clock: fc,
stopped: stopped,
}
ft.run(d)
return ft
}
// Advance advances fakeClock to a new point in time, ensuring channels from any
// previous invocations of After are notified appropriately before returning
func (fc *fakeClock) Advance(d time.Duration) {
fc.l.Lock()
defer fc.l.Unlock()
end := fc.time.Add(d)
var newSleepers []*sleeper
for _, s := range fc.sleepers {
if end.Sub(s.until) >= 0 {
s.done <- end
} else {
newSleepers = append(newSleepers, s)
}
}
fc.sleepers = newSleepers
fc.blockers = notifyBlockers(fc.blockers, len(fc.sleepers))
fc.time = end
}
// BlockUntil will block until the fakeClock has the given number of sleepers
// (callers of Sleep or After)
func (fc *fakeClock) BlockUntil(n int) {
fc.l.Lock()
// Fast path: we already have >= n sleepers.
if len(fc.sleepers) >= n {
fc.l.Unlock()
return
}
// Otherwise, we have < n sleepers. Set up a new blocker to wait for more.
b := &blocker{
count: n,
ch: make(chan struct{}),
}
fc.blockers = append(fc.blockers, b)
fc.l.Unlock()
<-b.ch
}