-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclock.go
More file actions
153 lines (136 loc) · 3.92 KB
/
clock.go
File metadata and controls
153 lines (136 loc) · 3.92 KB
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
package statekit
import (
"sort"
"sync"
"time"
)
// Clock abstracts wall-clock timing so timer-driven behavior
// (delayed transitions, supervision timers) can be deterministically
// controlled in tests via FakeClock.
//
// Production code uses the default systemClock; pass FakeClock via
// WithClock for tests.
type Clock interface {
// AfterFunc schedules fn to run after d. The returned Timer.Stop
// cancels the callback if it has not yet fired.
AfterFunc(d time.Duration, fn func()) Timer
}
// Timer is the cancelable handle returned by Clock.AfterFunc. Matches
// the Stop semantics of *time.Timer.
type Timer interface {
// Stop prevents the callback from firing. Returns true if the
// call stops the timer, false if the timer has already expired
// or has been stopped.
Stop() bool
}
// systemClock is the default Clock — a thin wrapper over time.AfterFunc.
type systemClock struct{}
// AfterFunc delegates to time.AfterFunc.
func (systemClock) AfterFunc(d time.Duration, fn func()) Timer {
return time.AfterFunc(d, fn)
}
// SystemClock returns the default wall-clock implementation.
func SystemClock() Clock { return systemClock{} }
// fakeTimer is the Timer returned by FakeClock.AfterFunc.
type fakeTimer struct {
clock *FakeClock
id int
deadline time.Time
fn func()
stopped bool
}
// Stop removes the timer from its FakeClock if it has not yet fired.
func (t *fakeTimer) Stop() bool {
t.clock.mu.Lock()
defer t.clock.mu.Unlock()
if t.stopped {
return false
}
for i, candidate := range t.clock.timers {
if candidate == t {
t.clock.timers = append(t.clock.timers[:i], t.clock.timers[i+1:]...)
t.stopped = true
return true
}
}
t.stopped = true
return false
}
// FakeClock is a test-only Clock whose passage of time is controlled
// by Advance and Now. Safe for concurrent use; callbacks run on the
// goroutine that calls Advance.
type FakeClock struct {
mu sync.Mutex
now time.Time
timers []*fakeTimer
nextID int
}
// NewFakeClock returns a FakeClock anchored at the given time. Use
// time.Now() (or any deterministic timestamp) as the anchor.
func NewFakeClock(anchor time.Time) *FakeClock {
return &FakeClock{now: anchor}
}
// Now returns the FakeClock's current time. The clock does not advance
// on its own — call Advance to move it forward.
func (c *FakeClock) Now() time.Time {
c.mu.Lock()
defer c.mu.Unlock()
return c.now
}
// AfterFunc schedules fn to fire when the clock advances past d.
func (c *FakeClock) AfterFunc(d time.Duration, fn func()) Timer {
c.mu.Lock()
defer c.mu.Unlock()
c.nextID++
t := &fakeTimer{
clock: c,
id: c.nextID,
deadline: c.now.Add(d),
fn: fn,
}
c.timers = append(c.timers, t)
return t
}
// Advance moves the clock forward by d, firing any callbacks whose
// deadline falls within the new interval. Callbacks fire in deadline
// order; ties resolve by registration order.
//
// Callbacks run synchronously on the caller's goroutine before
// Advance returns. This makes timer-driven tests deterministic.
func (c *FakeClock) Advance(d time.Duration) {
c.mu.Lock()
c.now = c.now.Add(d)
target := c.now
// Pull out timers whose deadline has passed; sort them by deadline.
due := make([]*fakeTimer, 0, len(c.timers))
keep := c.timers[:0]
for _, t := range c.timers {
if !t.deadline.After(target) {
due = append(due, t)
} else {
keep = append(keep, t)
}
}
c.timers = keep
c.mu.Unlock()
sort.SliceStable(due, func(i, j int) bool {
if due[i].deadline.Equal(due[j].deadline) {
return due[i].id < due[j].id
}
return due[i].deadline.Before(due[j].deadline)
})
for _, t := range due {
// Mark stopped so a late Stop() call is a no-op.
c.mu.Lock()
t.stopped = true
c.mu.Unlock()
t.fn()
}
}
// PendingTimers reports how many timers are scheduled and not yet
// fired or stopped. Useful for test invariants.
func (c *FakeClock) PendingTimers() int {
c.mu.Lock()
defer c.mu.Unlock()
return len(c.timers)
}