cubefs
386 строк · 9.5 Кб
1package clock
2
3import (
4"context"
5"sort"
6"sync"
7"time"
8)
9
10// Re-export of time.Duration
11type Duration = time.Duration
12
13// Clock represents an interface to the functions in the standard library time
14// package. Two implementations are available in the clock package. The first
15// is a real-time clock which simply wraps the time package's functions. The
16// second is a mock clock which will only change when
17// programmatically adjusted.
18type Clock interface {
19After(d time.Duration) <-chan time.Time
20AfterFunc(d time.Duration, f func()) *Timer
21Now() time.Time
22Since(t time.Time) time.Duration
23Until(t time.Time) time.Duration
24Sleep(d time.Duration)
25Tick(d time.Duration) <-chan time.Time
26Ticker(d time.Duration) *Ticker
27Timer(d time.Duration) *Timer
28WithDeadline(parent context.Context, d time.Time) (context.Context, context.CancelFunc)
29WithTimeout(parent context.Context, t time.Duration) (context.Context, context.CancelFunc)
30}
31
32// New returns an instance of a real-time clock.
33func New() Clock {
34return &clock{}
35}
36
37// clock implements a real-time clock by simply wrapping the time package functions.
38type clock struct{}
39
40func (c *clock) After(d time.Duration) <-chan time.Time { return time.After(d) }
41
42func (c *clock) AfterFunc(d time.Duration, f func()) *Timer {
43return &Timer{timer: time.AfterFunc(d, f)}
44}
45
46func (c *clock) Now() time.Time { return time.Now() }
47
48func (c *clock) Since(t time.Time) time.Duration { return time.Since(t) }
49
50func (c *clock) Until(t time.Time) time.Duration { return time.Until(t) }
51
52func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }
53
54func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) }
55
56func (c *clock) Ticker(d time.Duration) *Ticker {
57t := time.NewTicker(d)
58return &Ticker{C: t.C, ticker: t}
59}
60
61func (c *clock) Timer(d time.Duration) *Timer {
62t := time.NewTimer(d)
63return &Timer{C: t.C, timer: t}
64}
65
66func (c *clock) WithDeadline(parent context.Context, d time.Time) (context.Context, context.CancelFunc) {
67return context.WithDeadline(parent, d)
68}
69
70func (c *clock) WithTimeout(parent context.Context, t time.Duration) (context.Context, context.CancelFunc) {
71return context.WithTimeout(parent, t)
72}
73
74// Mock represents a mock clock that only moves forward programmically.
75// It can be preferable to a real-time clock when testing time-based functionality.
76type Mock struct {
77mu sync.Mutex
78now time.Time // current time
79timers clockTimers // tickers & timers
80}
81
82// NewMock returns an instance of a mock clock.
83// The current time of the mock clock on initialization is the Unix epoch.
84func NewMock() *Mock {
85return &Mock{now: time.Unix(0, 0)}
86}
87
88// Add moves the current time of the mock clock forward by the specified duration.
89// This should only be called from a single goroutine at a time.
90func (m *Mock) Add(d time.Duration) {
91// Calculate the final current time.
92t := m.now.Add(d)
93
94// Continue to execute timers until there are no more before the new time.
95for {
96if !m.runNextTimer(t) {
97break
98}
99}
100
101// Ensure that we end with the new time.
102m.mu.Lock()
103m.now = t
104m.mu.Unlock()
105
106// Give a small buffer to make sure that other goroutines get handled.
107gosched()
108}
109
110// Set sets the current time of the mock clock to a specific one.
111// This should only be called from a single goroutine at a time.
112func (m *Mock) Set(t time.Time) {
113// Continue to execute timers until there are no more before the new time.
114for {
115if !m.runNextTimer(t) {
116break
117}
118}
119
120// Ensure that we end with the new time.
121m.mu.Lock()
122m.now = t
123m.mu.Unlock()
124
125// Give a small buffer to make sure that other goroutines get handled.
126gosched()
127}
128
129// runNextTimer executes the next timer in chronological order and moves the
130// current time to the timer's next tick time. The next time is not executed if
131// its next time is after the max time. Returns true if a timer was executed.
132func (m *Mock) runNextTimer(max time.Time) bool {
133m.mu.Lock()
134
135// Sort timers by time.
136sort.Sort(m.timers)
137
138// If we have no more timers then exit.
139if len(m.timers) == 0 {
140m.mu.Unlock()
141return false
142}
143
144// Retrieve next timer. Exit if next tick is after new time.
145t := m.timers[0]
146if t.Next().After(max) {
147m.mu.Unlock()
148return false
149}
150
151// Move "now" forward and unlock clock.
152m.now = t.Next()
153m.mu.Unlock()
154
155// Execute timer.
156t.Tick(m.now)
157return true
158}
159
160// After waits for the duration to elapse and then sends the current time on the returned channel.
161func (m *Mock) After(d time.Duration) <-chan time.Time {
162return m.Timer(d).C
163}
164
165// AfterFunc waits for the duration to elapse and then executes a function.
166// A Timer is returned that can be stopped.
167func (m *Mock) AfterFunc(d time.Duration, f func()) *Timer {
168m.mu.Lock()
169defer m.mu.Unlock()
170ch := make(chan time.Time, 1)
171t := &Timer{
172c: ch,
173fn: f,
174mock: m,
175next: m.now.Add(d),
176stopped: false,
177}
178m.timers = append(m.timers, (*internalTimer)(t))
179return t
180}
181
182// Now returns the current wall time on the mock clock.
183func (m *Mock) Now() time.Time {
184m.mu.Lock()
185defer m.mu.Unlock()
186return m.now
187}
188
189// Since returns time since `t` using the mock clock's wall time.
190func (m *Mock) Since(t time.Time) time.Duration {
191return m.Now().Sub(t)
192}
193
194// Until returns time until `t` using the mock clock's wall time.
195func (m *Mock) Until(t time.Time) time.Duration {
196return t.Sub(m.Now())
197}
198
199// Sleep pauses the goroutine for the given duration on the mock clock.
200// The clock must be moved forward in a separate goroutine.
201func (m *Mock) Sleep(d time.Duration) {
202<-m.After(d)
203}
204
205// Tick is a convenience function for Ticker().
206// It will return a ticker channel that cannot be stopped.
207func (m *Mock) Tick(d time.Duration) <-chan time.Time {
208return m.Ticker(d).C
209}
210
211// Ticker creates a new instance of Ticker.
212func (m *Mock) Ticker(d time.Duration) *Ticker {
213m.mu.Lock()
214defer m.mu.Unlock()
215ch := make(chan time.Time, 1)
216t := &Ticker{
217C: ch,
218c: ch,
219mock: m,
220d: d,
221next: m.now.Add(d),
222}
223m.timers = append(m.timers, (*internalTicker)(t))
224return t
225}
226
227// Timer creates a new instance of Timer.
228func (m *Mock) Timer(d time.Duration) *Timer {
229m.mu.Lock()
230defer m.mu.Unlock()
231ch := make(chan time.Time, 1)
232t := &Timer{
233C: ch,
234c: ch,
235mock: m,
236next: m.now.Add(d),
237stopped: false,
238}
239m.timers = append(m.timers, (*internalTimer)(t))
240return t
241}
242
243func (m *Mock) removeClockTimer(t clockTimer) {
244for i, timer := range m.timers {
245if timer == t {
246copy(m.timers[i:], m.timers[i+1:])
247m.timers[len(m.timers)-1] = nil
248m.timers = m.timers[:len(m.timers)-1]
249break
250}
251}
252sort.Sort(m.timers)
253}
254
255// clockTimer represents an object with an associated start time.
256type clockTimer interface {
257Next() time.Time
258Tick(time.Time)
259}
260
261// clockTimers represents a list of sortable timers.
262type clockTimers []clockTimer
263
264func (a clockTimers) Len() int { return len(a) }
265func (a clockTimers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
266func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next()) }
267
268// Timer represents a single event.
269// The current time will be sent on C, unless the timer was created by AfterFunc.
270type Timer struct {
271C <-chan time.Time
272c chan time.Time
273timer *time.Timer // realtime impl, if set
274next time.Time // next tick time
275mock *Mock // mock clock, if set
276fn func() // AfterFunc function, if set
277stopped bool // True if stopped, false if running
278}
279
280// Stop turns off the ticker.
281func (t *Timer) Stop() bool {
282if t.timer != nil {
283return t.timer.Stop()
284}
285
286t.mock.mu.Lock()
287registered := !t.stopped
288t.mock.removeClockTimer((*internalTimer)(t))
289t.stopped = true
290t.mock.mu.Unlock()
291return registered
292}
293
294// Reset changes the expiry time of the timer
295func (t *Timer) Reset(d time.Duration) bool {
296if t.timer != nil {
297return t.timer.Reset(d)
298}
299
300t.mock.mu.Lock()
301t.next = t.mock.now.Add(d)
302defer t.mock.mu.Unlock()
303
304registered := !t.stopped
305if t.stopped {
306t.mock.timers = append(t.mock.timers, (*internalTimer)(t))
307}
308
309t.stopped = false
310return registered
311}
312
313type internalTimer Timer
314
315func (t *internalTimer) Next() time.Time { return t.next }
316func (t *internalTimer) Tick(now time.Time) {
317// a gosched() after ticking, to allow any consequences of the
318// tick to complete
319defer gosched()
320
321t.mock.mu.Lock()
322if t.fn != nil {
323// defer function execution until the lock is released, and
324defer t.fn()
325} else {
326t.c <- now
327}
328t.mock.removeClockTimer((*internalTimer)(t))
329t.stopped = true
330t.mock.mu.Unlock()
331}
332
333// Ticker holds a channel that receives "ticks" at regular intervals.
334type Ticker struct {
335C <-chan time.Time
336c chan time.Time
337ticker *time.Ticker // realtime impl, if set
338next time.Time // next tick time
339mock *Mock // mock clock, if set
340d time.Duration // time between ticks
341}
342
343// Stop turns off the ticker.
344func (t *Ticker) Stop() {
345if t.ticker != nil {
346t.ticker.Stop()
347} else {
348t.mock.mu.Lock()
349t.mock.removeClockTimer((*internalTicker)(t))
350t.mock.mu.Unlock()
351}
352}
353
354// Reset resets the ticker to a new duration.
355func (t *Ticker) Reset(dur time.Duration) {
356if t.ticker != nil {
357t.ticker.Reset(dur)
358return
359}
360
361t.mock.mu.Lock()
362defer t.mock.mu.Unlock()
363
364t.d = dur
365t.next = t.mock.now.Add(dur)
366}
367
368type internalTicker Ticker
369
370func (t *internalTicker) Next() time.Time { return t.next }
371func (t *internalTicker) Tick(now time.Time) {
372select {
373case t.c <- now:
374default:
375}
376t.next = now.Add(t.d)
377gosched()
378}
379
380// Sleep momentarily so that other goroutines can process.
381func gosched() { time.Sleep(1 * time.Millisecond) }
382
383var (
384// type checking
385_ Clock = &Mock{}
386)
387