Skip to content

Commit c22fdec

Browse files
authored
common/mclock: add NewTimer and Timer.Reset (ethereum#20634)
These methods can be helpful when migrating existing timer code.
1 parent dcffb77 commit c22fdec

File tree

3 files changed

+207
-64
lines changed

3 files changed

+207
-64
lines changed

common/mclock/mclock.go

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,44 +31,93 @@ func Now() AbsTime {
3131
return AbsTime(monotime.Now())
3232
}
3333

34-
// Add returns t + d.
34+
// Add returns t + d as absolute time.
3535
func (t AbsTime) Add(d time.Duration) AbsTime {
3636
return t + AbsTime(d)
3737
}
3838

39+
// Sub returns t - t2 as a duration.
40+
func (t AbsTime) Sub(t2 AbsTime) time.Duration {
41+
return time.Duration(t - t2)
42+
}
43+
3944
// The Clock interface makes it possible to replace the monotonic system clock with
4045
// a simulated clock.
4146
type Clock interface {
4247
Now() AbsTime
4348
Sleep(time.Duration)
44-
After(time.Duration) <-chan time.Time
49+
NewTimer(time.Duration) ChanTimer
50+
After(time.Duration) <-chan AbsTime
4551
AfterFunc(d time.Duration, f func()) Timer
4652
}
4753

48-
// Timer represents a cancellable event returned by AfterFunc
54+
// Timer is a cancellable event created by AfterFunc.
4955
type Timer interface {
56+
// Stop cancels the timer. It returns false if the timer has already
57+
// expired or been stopped.
5058
Stop() bool
5159
}
5260

61+
// ChanTimer is a cancellable event created by NewTimer.
62+
type ChanTimer interface {
63+
Timer
64+
65+
// The channel returned by C receives a value when the timer expires.
66+
C() <-chan AbsTime
67+
// Reset reschedules the timer with a new timeout.
68+
// It should be invoked only on stopped or expired timers with drained channels.
69+
Reset(time.Duration)
70+
}
71+
5372
// System implements Clock using the system clock.
5473
type System struct{}
5574

5675
// Now returns the current monotonic time.
57-
func (System) Now() AbsTime {
76+
func (c System) Now() AbsTime {
5877
return AbsTime(monotime.Now())
5978
}
6079

6180
// Sleep blocks for the given duration.
62-
func (System) Sleep(d time.Duration) {
81+
func (c System) Sleep(d time.Duration) {
6382
time.Sleep(d)
6483
}
6584

85+
// NewTimer creates a timer which can be rescheduled.
86+
func (c System) NewTimer(d time.Duration) ChanTimer {
87+
ch := make(chan AbsTime, 1)
88+
t := time.AfterFunc(d, func() {
89+
// This send is non-blocking because that's how time.Timer
90+
// behaves. It doesn't matter in the happy case, but does
91+
// when Reset is misused.
92+
select {
93+
case ch <- c.Now():
94+
default:
95+
}
96+
})
97+
return &systemTimer{t, ch}
98+
}
99+
66100
// After returns a channel which receives the current time after d has elapsed.
67-
func (System) After(d time.Duration) <-chan time.Time {
68-
return time.After(d)
101+
func (c System) After(d time.Duration) <-chan AbsTime {
102+
ch := make(chan AbsTime, 1)
103+
time.AfterFunc(d, func() { ch <- c.Now() })
104+
return ch
69105
}
70106

71107
// AfterFunc runs f on a new goroutine after the duration has elapsed.
72-
func (System) AfterFunc(d time.Duration, f func()) Timer {
108+
func (c System) AfterFunc(d time.Duration, f func()) Timer {
73109
return time.AfterFunc(d, f)
74110
}
111+
112+
type systemTimer struct {
113+
*time.Timer
114+
ch <-chan AbsTime
115+
}
116+
117+
func (st *systemTimer) Reset(d time.Duration) {
118+
st.Timer.Reset(d)
119+
}
120+
121+
func (st *systemTimer) C() <-chan AbsTime {
122+
return st.ch
123+
}

common/mclock/simclock.go

Lines changed: 95 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package mclock
1818

1919
import (
20+
"container/heap"
2021
"sync"
2122
"time"
2223
)
@@ -32,18 +33,24 @@ import (
3233
// the timeout using a channel or semaphore.
3334
type Simulated struct {
3435
now AbsTime
35-
scheduled []*simTimer
36+
scheduled simTimerHeap
3637
mu sync.RWMutex
3738
cond *sync.Cond
38-
lastId uint64
3939
}
4040

41-
// simTimer implements Timer on the virtual clock.
41+
// simTimer implements ChanTimer on the virtual clock.
4242
type simTimer struct {
43-
do func()
44-
at AbsTime
45-
id uint64
46-
s *Simulated
43+
at AbsTime
44+
index int // position in s.scheduled
45+
s *Simulated
46+
do func()
47+
ch <-chan AbsTime
48+
}
49+
50+
func (s *Simulated) init() {
51+
if s.cond == nil {
52+
s.cond = sync.NewCond(&s.mu)
53+
}
4754
}
4855

4956
// Run moves the clock by the given duration, executing all timers before that duration.
@@ -53,14 +60,9 @@ func (s *Simulated) Run(d time.Duration) {
5360

5461
end := s.now + AbsTime(d)
5562
var do []func()
56-
for len(s.scheduled) > 0 {
57-
ev := s.scheduled[0]
58-
if ev.at > end {
59-
break
60-
}
61-
s.now = ev.at
63+
for len(s.scheduled) > 0 && s.scheduled[0].at <= end {
64+
ev := heap.Pop(&s.scheduled).(*simTimer)
6265
do = append(do, ev.do)
63-
s.scheduled = s.scheduled[1:]
6466
}
6567
s.now = end
6668
s.mu.Unlock()
@@ -102,61 +104,106 @@ func (s *Simulated) Sleep(d time.Duration) {
102104
<-s.After(d)
103105
}
104106

107+
// NewTimer creates a timer which fires when the clock has advanced by d.
108+
func (s *Simulated) NewTimer(d time.Duration) ChanTimer {
109+
s.mu.Lock()
110+
defer s.mu.Unlock()
111+
112+
ch := make(chan AbsTime, 1)
113+
var timer *simTimer
114+
timer = s.schedule(d, func() { ch <- timer.at })
115+
timer.ch = ch
116+
return timer
117+
}
118+
105119
// After returns a channel which receives the current time after the clock
106120
// has advanced by d.
107-
func (s *Simulated) After(d time.Duration) <-chan time.Time {
108-
after := make(chan time.Time, 1)
109-
s.AfterFunc(d, func() {
110-
after <- (time.Time{}).Add(time.Duration(s.now))
111-
})
112-
return after
121+
func (s *Simulated) After(d time.Duration) <-chan AbsTime {
122+
return s.NewTimer(d).C()
113123
}
114124

115125
// AfterFunc runs fn after the clock has advanced by d. Unlike with the system
116126
// clock, fn runs on the goroutine that calls Run.
117127
func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
118128
s.mu.Lock()
119129
defer s.mu.Unlock()
130+
131+
return s.schedule(d, fn)
132+
}
133+
134+
func (s *Simulated) schedule(d time.Duration, fn func()) *simTimer {
120135
s.init()
121136

122137
at := s.now + AbsTime(d)
123-
s.lastId++
124-
id := s.lastId
125-
l, h := 0, len(s.scheduled)
126-
ll := h
127-
for l != h {
128-
m := (l + h) / 2
129-
if (at < s.scheduled[m].at) || ((at == s.scheduled[m].at) && (id < s.scheduled[m].id)) {
130-
h = m
131-
} else {
132-
l = m + 1
133-
}
134-
}
135138
ev := &simTimer{do: fn, at: at, s: s}
136-
s.scheduled = append(s.scheduled, nil)
137-
copy(s.scheduled[l+1:], s.scheduled[l:ll])
138-
s.scheduled[l] = ev
139+
heap.Push(&s.scheduled, ev)
139140
s.cond.Broadcast()
140141
return ev
141142
}
142143

143144
func (ev *simTimer) Stop() bool {
144-
s := ev.s
145-
s.mu.Lock()
146-
defer s.mu.Unlock()
145+
ev.s.mu.Lock()
146+
defer ev.s.mu.Unlock()
147147

148-
for i := 0; i < len(s.scheduled); i++ {
149-
if s.scheduled[i] == ev {
150-
s.scheduled = append(s.scheduled[:i], s.scheduled[i+1:]...)
151-
s.cond.Broadcast()
152-
return true
153-
}
148+
if ev.index < 0 {
149+
return false
154150
}
155-
return false
151+
heap.Remove(&ev.s.scheduled, ev.index)
152+
ev.s.cond.Broadcast()
153+
ev.index = -1
154+
return true
156155
}
157156

158-
func (s *Simulated) init() {
159-
if s.cond == nil {
160-
s.cond = sync.NewCond(&s.mu)
157+
func (ev *simTimer) Reset(d time.Duration) {
158+
if ev.ch == nil {
159+
panic("mclock: Reset() on timer created by AfterFunc")
161160
}
161+
162+
ev.s.mu.Lock()
163+
defer ev.s.mu.Unlock()
164+
ev.at = ev.s.now.Add(d)
165+
if ev.index < 0 {
166+
heap.Push(&ev.s.scheduled, ev) // already expired
167+
} else {
168+
heap.Fix(&ev.s.scheduled, ev.index) // hasn't fired yet, reschedule
169+
}
170+
ev.s.cond.Broadcast()
171+
}
172+
173+
func (ev *simTimer) C() <-chan AbsTime {
174+
if ev.ch == nil {
175+
panic("mclock: C() on timer created by AfterFunc")
176+
}
177+
return ev.ch
178+
}
179+
180+
type simTimerHeap []*simTimer
181+
182+
func (h *simTimerHeap) Len() int {
183+
return len(*h)
184+
}
185+
186+
func (h *simTimerHeap) Less(i, j int) bool {
187+
return (*h)[i].at < (*h)[j].at
188+
}
189+
190+
func (h *simTimerHeap) Swap(i, j int) {
191+
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
192+
(*h)[i].index = i
193+
(*h)[j].index = j
194+
}
195+
196+
func (h *simTimerHeap) Push(x interface{}) {
197+
t := x.(*simTimer)
198+
t.index = len(*h)
199+
*h = append(*h, t)
200+
}
201+
202+
func (h *simTimerHeap) Pop() interface{} {
203+
end := len(*h) - 1
204+
t := (*h)[end]
205+
t.index = -1
206+
(*h)[end] = nil
207+
*h = (*h)[:end]
208+
return t
162209
}

common/mclock/simclock_test.go

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ var _ Clock = System{}
2525
var _ Clock = new(Simulated)
2626

2727
func TestSimulatedAfter(t *testing.T) {
28-
const timeout = 30 * time.Minute
29-
const adv = time.Minute
30-
3128
var (
32-
c Simulated
33-
end = c.Now().Add(timeout)
34-
ch = c.After(timeout)
29+
timeout = 30 * time.Minute
30+
offset = 99 * time.Hour
31+
adv = 11 * time.Minute
32+
c Simulated
3533
)
34+
c.Run(offset)
35+
36+
end := c.Now().Add(timeout)
37+
ch := c.After(timeout)
3638
for c.Now() < end.Add(-adv) {
3739
c.Run(adv)
3840
select {
@@ -45,8 +47,8 @@ func TestSimulatedAfter(t *testing.T) {
4547
c.Run(adv)
4648
select {
4749
case stamp := <-ch:
48-
want := time.Time{}.Add(timeout)
49-
if !stamp.Equal(want) {
50+
want := AbsTime(0).Add(offset).Add(timeout)
51+
if stamp != want {
5052
t.Errorf("Wrong time sent on timer channel: got %v, want %v", stamp, want)
5153
}
5254
default:
@@ -113,3 +115,48 @@ func TestSimulatedSleep(t *testing.T) {
113115
t.Fatal("Sleep didn't return in time")
114116
}
115117
}
118+
119+
func TestSimulatedTimerReset(t *testing.T) {
120+
var (
121+
c Simulated
122+
timeout = 1 * time.Hour
123+
)
124+
timer := c.NewTimer(timeout)
125+
c.Run(2 * timeout)
126+
select {
127+
case ftime := <-timer.C():
128+
if ftime != AbsTime(timeout) {
129+
t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(timeout))
130+
}
131+
default:
132+
t.Fatal("timer didn't fire")
133+
}
134+
135+
timer.Reset(timeout)
136+
c.Run(2 * timeout)
137+
select {
138+
case ftime := <-timer.C():
139+
if ftime != AbsTime(3*timeout) {
140+
t.Fatalf("wrong time %v sent on timer channel, want %v", ftime, AbsTime(3*timeout))
141+
}
142+
default:
143+
t.Fatal("timer didn't fire again")
144+
}
145+
}
146+
147+
func TestSimulatedTimerStop(t *testing.T) {
148+
var (
149+
c Simulated
150+
timeout = 1 * time.Hour
151+
)
152+
timer := c.NewTimer(timeout)
153+
c.Run(2 * timeout)
154+
if timer.Stop() {
155+
t.Errorf("Stop returned true for fired timer")
156+
}
157+
select {
158+
case <-timer.C():
159+
default:
160+
t.Fatal("timer didn't fire")
161+
}
162+
}

0 commit comments

Comments
 (0)