Skip to content

Commit b1c3010

Browse files
authored
common/mclock: clean up AfterFunc support (#20054)
This change adds tests for the virtual clock and aligns the interface with the time package by renaming Cancel to Stop. It also removes the binary search from Stop because it complicates the code unnecessarily.
1 parent aff9869 commit b1c3010

File tree

4 files changed

+158
-65
lines changed

4 files changed

+158
-65
lines changed

common/mclock/mclock.go

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,39 @@ func (t AbsTime) Add(d time.Duration) AbsTime {
3636
return t + AbsTime(d)
3737
}
3838

39-
// Clock interface makes it possible to replace the monotonic system clock with
39+
// The Clock interface makes it possible to replace the monotonic system clock with
4040
// a simulated clock.
4141
type Clock interface {
4242
Now() AbsTime
4343
Sleep(time.Duration)
4444
After(time.Duration) <-chan time.Time
45-
AfterFunc(d time.Duration, f func()) Event
45+
AfterFunc(d time.Duration, f func()) Timer
4646
}
4747

48-
// Event represents a cancellable event returned by AfterFunc
49-
type Event interface {
50-
Cancel() bool
48+
// Timer represents a cancellable event returned by AfterFunc
49+
type Timer interface {
50+
Stop() bool
5151
}
5252

5353
// System implements Clock using the system clock.
5454
type System struct{}
5555

56-
// Now implements Clock.
56+
// Now returns the current monotonic time.
5757
func (System) Now() AbsTime {
5858
return AbsTime(monotime.Now())
5959
}
6060

61-
// Sleep implements Clock.
61+
// Sleep blocks for the given duration.
6262
func (System) Sleep(d time.Duration) {
6363
time.Sleep(d)
6464
}
6565

66-
// After implements Clock.
66+
// After returns a channel which receives the current time after d has elapsed.
6767
func (System) After(d time.Duration) <-chan time.Time {
6868
return time.After(d)
6969
}
7070

71-
// AfterFunc implements Clock.
72-
func (System) AfterFunc(d time.Duration, f func()) Event {
73-
return (*SystemEvent)(time.AfterFunc(d, f))
74-
}
75-
76-
// SystemEvent implements Event using time.Timer.
77-
type SystemEvent time.Timer
78-
79-
// Cancel implements Event.
80-
func (e *SystemEvent) Cancel() bool {
81-
return (*time.Timer)(e).Stop()
71+
// AfterFunc runs f on a new goroutine after the duration has elapsed.
72+
func (System) AfterFunc(d time.Duration, f func()) Timer {
73+
return time.AfterFunc(d, f)
8274
}

common/mclock/simclock.go

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,17 @@ import (
3232
// the timeout using a channel or semaphore.
3333
type Simulated struct {
3434
now AbsTime
35-
scheduled []event
35+
scheduled []*simTimer
3636
mu sync.RWMutex
3737
cond *sync.Cond
3838
lastId uint64
3939
}
4040

41-
type event struct {
41+
// simTimer implements Timer on the virtual clock.
42+
type simTimer struct {
4243
do func()
4344
at AbsTime
4445
id uint64
45-
}
46-
47-
// SimulatedEvent implements Event for a virtual clock.
48-
type SimulatedEvent struct {
49-
at AbsTime
50-
id uint64
5146
s *Simulated
5247
}
5348

@@ -75,13 +70,15 @@ func (s *Simulated) Run(d time.Duration) {
7570
}
7671
}
7772

73+
// ActiveTimers returns the number of timers that haven't fired.
7874
func (s *Simulated) ActiveTimers() int {
7975
s.mu.RLock()
8076
defer s.mu.RUnlock()
8177

8278
return len(s.scheduled)
8379
}
8480

81+
// WaitForTimers waits until the clock has at least n scheduled timers.
8582
func (s *Simulated) WaitForTimers(n int) {
8683
s.mu.Lock()
8784
defer s.mu.Unlock()
@@ -92,20 +89,21 @@ func (s *Simulated) WaitForTimers(n int) {
9289
}
9390
}
9491

95-
// Now implements Clock.
92+
// Now returns the current virtual time.
9693
func (s *Simulated) Now() AbsTime {
9794
s.mu.RLock()
9895
defer s.mu.RUnlock()
9996

10097
return s.now
10198
}
10299

103-
// Sleep implements Clock.
100+
// Sleep blocks until the clock has advanced by d.
104101
func (s *Simulated) Sleep(d time.Duration) {
105102
<-s.After(d)
106103
}
107104

108-
// After implements Clock.
105+
// After returns a channel which receives the current time after the clock
106+
// has advanced by d.
109107
func (s *Simulated) After(d time.Duration) <-chan time.Time {
110108
after := make(chan time.Time, 1)
111109
s.AfterFunc(d, func() {
@@ -114,8 +112,9 @@ func (s *Simulated) After(d time.Duration) <-chan time.Time {
114112
return after
115113
}
116114

117-
// AfterFunc implements Clock.
118-
func (s *Simulated) AfterFunc(d time.Duration, do func()) Event {
115+
// AfterFunc runs fn after the clock has advanced by d. Unlike with the system
116+
// clock, fn runs on the goroutine that calls Run.
117+
func (s *Simulated) AfterFunc(d time.Duration, fn func()) Timer {
119118
s.mu.Lock()
120119
defer s.mu.Unlock()
121120
s.init()
@@ -133,44 +132,31 @@ func (s *Simulated) AfterFunc(d time.Duration, do func()) Event {
133132
l = m + 1
134133
}
135134
}
136-
s.scheduled = append(s.scheduled, event{})
135+
ev := &simTimer{do: fn, at: at, s: s}
136+
s.scheduled = append(s.scheduled, nil)
137137
copy(s.scheduled[l+1:], s.scheduled[l:ll])
138-
e := event{do: do, at: at, id: id}
139-
s.scheduled[l] = e
138+
s.scheduled[l] = ev
140139
s.cond.Broadcast()
141-
return &SimulatedEvent{at: at, id: id, s: s}
142-
}
143-
144-
func (s *Simulated) init() {
145-
if s.cond == nil {
146-
s.cond = sync.NewCond(&s.mu)
147-
}
140+
return ev
148141
}
149142

150-
// Cancel implements Event.
151-
func (e *SimulatedEvent) Cancel() bool {
152-
s := e.s
143+
func (ev *simTimer) Stop() bool {
144+
s := ev.s
153145
s.mu.Lock()
154146
defer s.mu.Unlock()
155147

156-
l, h := 0, len(s.scheduled)
157-
ll := h
158-
for l != h {
159-
m := (l + h) / 2
160-
if e.id == s.scheduled[m].id {
161-
l = m
162-
break
163-
}
164-
if (e.at < s.scheduled[m].at) || ((e.at == s.scheduled[m].at) && (e.id < s.scheduled[m].id)) {
165-
h = m
166-
} else {
167-
l = m + 1
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
168153
}
169154
}
170-
if l >= ll || s.scheduled[l].id != e.id {
171-
return false
155+
return false
156+
}
157+
158+
func (s *Simulated) init() {
159+
if s.cond == nil {
160+
s.cond = sync.NewCond(&s.mu)
172161
}
173-
copy(s.scheduled[l:ll-1], s.scheduled[l+1:])
174-
s.scheduled = s.scheduled[:ll-1]
175-
return true
176162
}

common/mclock/simclock_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2018 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package mclock
18+
19+
import (
20+
"testing"
21+
"time"
22+
)
23+
24+
var _ Clock = System{}
25+
var _ Clock = new(Simulated)
26+
27+
func TestSimulatedAfter(t *testing.T) {
28+
const timeout = 30 * time.Minute
29+
const adv = time.Minute
30+
31+
var (
32+
c Simulated
33+
end = c.Now().Add(timeout)
34+
ch = c.After(timeout)
35+
)
36+
for c.Now() < end.Add(-adv) {
37+
c.Run(adv)
38+
select {
39+
case <-ch:
40+
t.Fatal("Timer fired early")
41+
default:
42+
}
43+
}
44+
45+
c.Run(adv)
46+
select {
47+
case stamp := <-ch:
48+
want := time.Time{}.Add(timeout)
49+
if !stamp.Equal(want) {
50+
t.Errorf("Wrong time sent on timer channel: got %v, want %v", stamp, want)
51+
}
52+
default:
53+
t.Fatal("Timer didn't fire")
54+
}
55+
}
56+
57+
func TestSimulatedAfterFunc(t *testing.T) {
58+
var c Simulated
59+
60+
called1 := false
61+
timer1 := c.AfterFunc(100*time.Millisecond, func() { called1 = true })
62+
if c.ActiveTimers() != 1 {
63+
t.Fatalf("%d active timers, want one", c.ActiveTimers())
64+
}
65+
if fired := timer1.Stop(); !fired {
66+
t.Fatal("Stop returned false even though timer didn't fire")
67+
}
68+
if c.ActiveTimers() != 0 {
69+
t.Fatalf("%d active timers, want zero", c.ActiveTimers())
70+
}
71+
if called1 {
72+
t.Fatal("timer 1 called")
73+
}
74+
if fired := timer1.Stop(); fired {
75+
t.Fatal("Stop returned true after timer was already stopped")
76+
}
77+
78+
called2 := false
79+
timer2 := c.AfterFunc(100*time.Millisecond, func() { called2 = true })
80+
c.Run(50 * time.Millisecond)
81+
if called2 {
82+
t.Fatal("timer 2 called")
83+
}
84+
c.Run(51 * time.Millisecond)
85+
if !called2 {
86+
t.Fatal("timer 2 not called")
87+
}
88+
if fired := timer2.Stop(); fired {
89+
t.Fatal("Stop returned true after timer has fired")
90+
}
91+
}
92+
93+
func TestSimulatedSleep(t *testing.T) {
94+
var (
95+
c Simulated
96+
timeout = 1 * time.Hour
97+
done = make(chan AbsTime)
98+
)
99+
go func() {
100+
c.Sleep(timeout)
101+
done <- c.Now()
102+
}()
103+
104+
c.WaitForTimers(1)
105+
c.Run(2 * timeout)
106+
select {
107+
case stamp := <-done:
108+
want := AbsTime(2 * timeout)
109+
if stamp != want {
110+
t.Errorf("Wrong time after sleep: got %v, want %v", stamp, want)
111+
}
112+
case <-time.After(5 * time.Second):
113+
t.Fatal("Sleep didn't return in time")
114+
}
115+
}

les/balance.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type balanceTracker struct {
4242
negTimeFactor, negRequestFactor float64
4343
sumReqCost uint64
4444
lastUpdate, nextUpdate, initTime mclock.AbsTime
45-
updateEvent mclock.Event
45+
updateEvent mclock.Timer
4646
// since only a limited and fixed number of callbacks are needed, they are
4747
// stored in a fixed size array ordered by priority threshold.
4848
callbacks [balanceCallbackCount]balanceCallback
@@ -86,7 +86,7 @@ func (bt *balanceTracker) stop(now mclock.AbsTime) {
8686
bt.timeFactor = 0
8787
bt.requestFactor = 0
8888
if bt.updateEvent != nil {
89-
bt.updateEvent.Cancel()
89+
bt.updateEvent.Stop()
9090
bt.updateEvent = nil
9191
}
9292
}
@@ -235,7 +235,7 @@ func (bt *balanceTracker) checkCallbacks(now mclock.AbsTime) {
235235

236236
// updateAfter schedules a balance update and callback check in the future
237237
func (bt *balanceTracker) updateAfter(dt time.Duration) {
238-
if bt.updateEvent == nil || bt.updateEvent.Cancel() {
238+
if bt.updateEvent == nil || bt.updateEvent.Stop() {
239239
if dt == 0 {
240240
bt.updateEvent = nil
241241
} else {

0 commit comments

Comments
 (0)