-
Notifications
You must be signed in to change notification settings - Fork 59
/
clockwork.go
319 lines (280 loc) · 8.82 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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// Package clockwork contains a simple fake clock for Go.
package clockwork
import (
"context"
"errors"
"slices"
"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
Until(t time.Time) time.Duration
NewTicker(d time.Duration) Ticker
NewTimer(d time.Duration) Timer
AfterFunc(d time.Duration, f func()) Timer
}
// 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{}
}
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) Until(t time.Time) time.Duration {
return t.Sub(rc.Now())
}
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)}
}
func (rc *realClock) AfterFunc(d time.Duration, f func()) Timer {
return realTimer{time.AfterFunc(d, f)}
}
// FakeClock provides an interface for a clock which can be manually advanced
// through time.
//
// FakeClock maintains a list of "waiters," which consists of all callers
// waiting on the underlying clock (i.e. Tickers and Timers including callers of
// Sleep or After). Users can call BlockUntil to block until the clock has an
// expected number of waiters.
type FakeClock struct {
// l protects all attributes of the clock, including all attributes of all
// waiters and blockers.
l sync.RWMutex
waiters []expirer
blockers []*blocker
time time.Time
}
// NewFakeClock returns a FakeClock implementation which can be
// manually advanced through time for testing. The initial time of the
// FakeClock will be the current system time.
//
// Tests that require a deterministic time must use NewFakeClockAt.
func NewFakeClock() *FakeClock {
return NewFakeClockAt(time.Now())
}
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
func NewFakeClockAt(t time.Time) *FakeClock {
return &FakeClock{
time: t,
}
}
// blocker is a caller of BlockUntil.
type blocker struct {
count int
// ch is closed when the underlying clock has the specified number of blockers.
ch chan struct{}
}
// expirer is a timer or ticker that expires at some point in the future.
type expirer interface {
// expire the expirer at the given time, returning the desired duration until
// the next expiration, if any.
expire(now time.Time) (next *time.Duration)
// Get and set the expiration time.
expiration() time.Time
setExpiration(time.Time)
}
// 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 {
return fc.NewTimer(d).Chan()
}
// 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()
defer fc.l.RUnlock()
return fc.time
}
// 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)
}
// Until returns the duration that has to pass from the given time on the fakeClock
// to reach the given time.
func (fc *FakeClock) Until(t time.Time) time.Duration {
return t.Sub(fc.Now())
}
// NewTicker returns a Ticker that will expire only after calls to
// FakeClock.Advance() have moved the clock past the given duration.
//
// The duration d must be greater than zero; if not, NewTicker will panic.
func (fc *FakeClock) NewTicker(d time.Duration) Ticker {
// Maintain parity with
// https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/time/tick.go;l=23-25
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
ft := newFakeTicker(fc, d)
fc.l.Lock()
defer fc.l.Unlock()
fc.setExpirer(ft, d)
return ft
}
// NewTimer returns a Timer that will fire only after calls to
// fakeClock.Advance() have moved the clock past the given duration.
func (fc *FakeClock) NewTimer(d time.Duration) Timer {
t, _ := fc.newTimer(d, nil)
return t
}
// AfterFunc mimics [time.AfterFunc]; it returns a Timer that will invoke the
// given function only after calls to fakeClock.Advance() have moved the clock
// past the given duration.
func (fc *FakeClock) AfterFunc(d time.Duration, f func()) Timer {
t, _ := fc.newTimer(d, f)
return t
}
// newTimer returns a new timer using an optional afterFunc and the time that
// timer expires.
func (fc *FakeClock) newTimer(d time.Duration, afterfunc func()) (*fakeTimer, time.Time) {
ft := newFakeTimer(fc, afterfunc)
fc.l.Lock()
defer fc.l.Unlock()
fc.setExpirer(ft, d)
return ft, ft.expiration()
}
// newTimerAtTime is like newTimer, but uses a time instead of a duration.
//
// It is used to ensure FakeClock's lock is held constant through calling
// fc.After(t.Sub(fc.Now())). It should not be exposed externally.
func (fc *FakeClock) newTimerAtTime(t time.Time, afterfunc func()) *fakeTimer {
ft := newFakeTimer(fc, afterfunc)
fc.l.Lock()
defer fc.l.Unlock()
fc.setExpirer(ft, t.Sub(fc.time))
return ft
}
// Advance advances fakeClock to a new point in time, ensuring waiters and
// blockers are notified appropriately before returning.
func (fc *FakeClock) Advance(d time.Duration) {
fc.l.Lock()
defer fc.l.Unlock()
end := fc.time.Add(d)
// Expire the earliest waiter until the earliest waiter's expiration is after
// end.
//
// We don't iterate because the callback of the waiter might register a new
// waiter, so the list of waiters might change as we execute this.
for len(fc.waiters) > 0 && !end.Before(fc.waiters[0].expiration()) {
w := fc.waiters[0]
fc.waiters = fc.waiters[1:]
// Use the waiter's expiration as the current time for this expiration.
now := w.expiration()
fc.time = now
if d := w.expire(now); d != nil {
// Set the new expiration if needed.
fc.setExpirer(w, *d)
}
}
fc.time = end
}
// BlockUntil blocks until the FakeClock has the given number of waiters.
//
// Prefer BlockUntilContext in new code, which offers context cancellation to
// prevent deadlock.
//
// Deprecated: New code should prefer BlockUntilContext.
func (fc *FakeClock) BlockUntil(n int) {
fc.BlockUntilContext(context.TODO(), n)
}
// BlockUntilContext blocks until the fakeClock has the given number of waiters
// or the context is cancelled.
func (fc *FakeClock) BlockUntilContext(ctx context.Context, n int) error {
b := fc.newBlocker(n)
if b == nil {
return nil
}
select {
case <-b.ch:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (fc *FakeClock) newBlocker(n int) *blocker {
fc.l.Lock()
defer fc.l.Unlock()
// Fast path: we already have >= n waiters.
if len(fc.waiters) >= n {
return nil
}
// Set up a new blocker to wait for more waiters.
b := &blocker{
count: n,
ch: make(chan struct{}),
}
fc.blockers = append(fc.blockers, b)
return b
}
// stop stops an expirer, returning true if the expirer was stopped.
func (fc *FakeClock) stop(e expirer) bool {
fc.l.Lock()
defer fc.l.Unlock()
return fc.stopExpirer(e)
}
// stopExpirer stops an expirer, returning true if the expirer was stopped.
//
// The caller must hold fc.l.
func (fc *FakeClock) stopExpirer(e expirer) bool {
idx := slices.Index(fc.waiters, e)
if idx == -1 {
return false
}
// Remove element, maintaining order, setting inaccessible elements to nil so
// they can be garbage collected.
copy(fc.waiters[idx:], fc.waiters[idx+1:])
fc.waiters[len(fc.waiters)-1] = nil
fc.waiters = fc.waiters[:len(fc.waiters)-1]
return true
}
// setExpirer sets an expirer to expire at a future point in time.
//
// The caller must hold fc.l.
func (fc *FakeClock) setExpirer(e expirer, d time.Duration) {
if d.Nanoseconds() <= 0 {
// Special case for timers with duration <= 0: trigger immediately, never
// reset.
//
// Tickers never get here, they panic if d is < 0.
e.expire(fc.time)
return
}
// Add the expirer to the set of waiters and notify any blockers.
e.setExpiration(fc.time.Add(d))
fc.waiters = append(fc.waiters, e)
slices.SortFunc(fc.waiters, func(a, b expirer) int {
return a.expiration().Compare(b.expiration())
})
// Notify blockers of our new waiter.
count := len(fc.waiters)
fc.blockers = slices.DeleteFunc(fc.blockers, func(b *blocker) bool {
if b.count <= count {
close(b.ch)
return true
}
return false
})
}