Skip to content

Commit a7b3ab4

Browse files
committed
Address code review comments
Signed-off-by: Yacov Manevich <yacov.manevich@avalabs.org>
1 parent bdf0063 commit a7b3ab4

File tree

3 files changed

+59
-64
lines changed

3 files changed

+59
-64
lines changed

snow/engine/common/timer.go

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"time"
99
)
1010

11-
// PreemptionSignal signals when to preempt the pendingTimeout of the timeout handler.
11+
// PreemptionSignal signals when to preempt the pendingTimeoutToken of the timeout handler.
1212
type PreemptionSignal struct {
1313
activateOnce sync.Once
1414
initOnce sync.Once
@@ -37,38 +37,39 @@ func (ps *PreemptionSignal) Preempt() {
3737
// Only a single timeout can be pending to be scheduled at any given time.
3838
// Once a preemption signal is closed, all timeouts are immediately dispatched.
3939
type timeoutScheduler struct {
40-
newTimer func(duration time.Duration) *time.Timer
41-
onTimeout func()
42-
preemptionSignal <-chan struct{}
43-
pendingTimeout chan struct{}
40+
newTimer func(duration time.Duration) *time.Timer
41+
onTimeout func()
42+
preemptionSignal <-chan struct{}
43+
pendingTimeoutToken chan struct{}
4444
}
4545

4646
// NewTimeoutScheduler constructs a new timeout scheduler with the given function to be invoked upon a timeout,
4747
// unless the preemptionSignal is closed and in which case it invokes the function immediately.
48-
func NewTimeoutScheduler(onTimeout func(), preemptionSignal <-chan struct{}, newTimer func(duration time.Duration) *time.Timer) *timeoutScheduler {
48+
func NewTimeoutScheduler(onTimeout func(), preemptionSignal <-chan struct{}) *timeoutScheduler {
4949
pendingTimout := make(chan struct{}, 1)
5050
pendingTimout <- struct{}{}
5151
return &timeoutScheduler{
52-
preemptionSignal: preemptionSignal,
53-
newTimer: newTimer,
54-
onTimeout: onTimeout,
55-
pendingTimeout: pendingTimout,
52+
preemptionSignal: preemptionSignal,
53+
newTimer: time.NewTimer,
54+
onTimeout: onTimeout,
55+
pendingTimeoutToken: pendingTimout,
5656
}
5757
}
5858

5959
// RegisterTimeout fires the function the timeout scheduler is initialized with no later than the given timeout.
6060
func (th *timeoutScheduler) RegisterTimeout(d time.Duration) {
61-
acquiredToken := th.acquirePendingTimeoutToken()
62-
preempted := th.preempted()
63-
64-
if !preempted && !acquiredToken {
61+
// There can only be a single timeout pending at any time, and once a timeout is scheduled,
62+
// we prevent future timeouts to be scheduled until the timeout triggers by taking the pendingTimeoutToken.
63+
// Any subsequent attempt to register a timeout would fail obtaining the pendingTimeoutToken,
64+
// and return.
65+
if !th.acquirePendingTimeoutToken() {
6566
return
6667
}
6768

68-
go th.scheduleTimeout(d, acquiredToken)
69+
go th.scheduleTimeout(d)
6970
}
7071

71-
func (th *timeoutScheduler) scheduleTimeout(d time.Duration, acquiredToken bool) {
72+
func (th *timeoutScheduler) scheduleTimeout(d time.Duration) {
7273
timer := th.newTimer(d)
7374
defer timer.Stop()
7475

@@ -79,37 +80,32 @@ func (th *timeoutScheduler) scheduleTimeout(d time.Duration, acquiredToken bool)
7980
case <-th.preemptionSignal:
8081
}
8182

82-
if acquiredToken {
83-
th.relinquishPendingTimeoutToken()
84-
}
85-
}
86-
87-
func (th *timeoutScheduler) preempted() bool {
88-
select {
89-
case <-th.preemptionSignal:
90-
return true
91-
default:
92-
return false
93-
}
83+
// Relinquish the pendingTimeoutToken.
84+
// This is needed to be done before onTimeout() is invoked,
85+
// and that's why onTimeout() is deferred to be called at the end of the function.
86+
// If we trigger the timeout prematurely before we relinquish the pendingTimeoutToken,
87+
// A subsequent timeout scheduling attempt that originates from the triggering of the current timeout
88+
// will fail, as the pendingTimeoutToken is not yet available.
89+
th.pendingTimeoutToken <- struct{}{}
9490
}
9591

9692
func (th *timeoutScheduler) acquirePendingTimeoutToken() bool {
9793
select {
98-
case <-th.pendingTimeout:
94+
case <-th.pendingTimeoutToken:
9995
return true
10096
default:
10197
return false
10298
}
10399
}
104100

105-
func (th *timeoutScheduler) relinquishPendingTimeoutToken() {
106-
th.pendingTimeout <- struct{}{}
107-
}
108-
109101
// TimeoutRegistrar describes the standard interface for specifying a timeout
110102
type TimeoutRegistrar interface {
111-
// RegisterTimeout specifies how much time to delay the next timeout message
112-
// by. If the subnet has been bootstrapped, the timeout will fire
113-
// immediately via calling Preempt().
103+
// RegisterTimeout specifies how much time to delay the next timeout message by.
104+
//
105+
// If there is already a pending timeout message, this call is a no-op.
106+
// However, it is guaranteed that the timeout will fire at least once after
107+
// calling this function.
108+
//
109+
// If the subnet has been bootstrapped, the timeout will fire immediately via calling Preempt().
114110
RegisterTimeout(time.Duration)
115111
}

snow/engine/common/timer_test.go

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ func TestTimeoutScheduler(t *testing.T) {
1919
advanceTime func(chan time.Time)
2020
}{
2121
{
22-
desc: "multiple pendingTimeout one after the other with preemption",
22+
desc: "multiple pendingTimeoutToken one after the other with preemption",
2323
expectedInvocationCount: 10,
2424
shouldPreempt: true,
2525
clock: make(chan time.Time, 1),
26-
initClock: func(_ chan time.Time) {},
27-
advanceTime: func(_ chan time.Time) {},
26+
initClock: func(chan time.Time) {},
27+
advanceTime: func(chan time.Time) {},
2828
},
2929
{
30-
desc: "multiple pendingTimeout one after the other",
30+
desc: "multiple pendingTimeoutToken one after the other",
3131
expectedInvocationCount: 10,
3232
clock: make(chan time.Time, 1),
3333
initClock: func(clock chan time.Time) {
@@ -57,22 +57,16 @@ func TestTimeoutScheduler(t *testing.T) {
5757
// in order to make the tests deterministic.
5858
order := make(chan struct{})
5959

60-
newTimer := func(_ time.Duration) *time.Timer {
61-
// We use a duration of 0 to not leave a lingering timer
62-
// after the test finishes.
63-
// Then we replace the time channel to have control over the timer.
64-
timer := time.NewTimer(0)
65-
timer.C = testCase.clock
66-
return timer
67-
}
60+
newTimer := makeMockedTimer(testCase.clock)
6861

6962
onTimeout := func() {
7063
order <- struct{}{}
7164
wg.Done()
7265
testCase.advanceTime(testCase.clock)
7366
}
7467

75-
ts := NewTimeoutScheduler(onTimeout, ps, newTimer)
68+
ts := NewTimeoutScheduler(onTimeout, ps)
69+
ts.newTimer = newTimer
7670

7771
for i := 0; i < testCase.expectedInvocationCount; i++ {
7872
ts.RegisterTimeout(time.Hour)
@@ -85,26 +79,19 @@ func TestTimeoutScheduler(t *testing.T) {
8579
}
8680

8781
func TestTimeoutSchedulerConcurrentRegister(_ *testing.T) {
82+
// Not enough invocations means the test would stall.
83+
// Too many invocations means a negative counter panic.
84+
8885
clock := make(chan time.Time, 2)
89-
newTimer := func(_ time.Duration) *time.Timer {
90-
// We use a duration of 0 to not leave a lingering timer
91-
// after the test finishes.
92-
// Then we replace the time channel to have control over the timer.
93-
timer := time.NewTimer(0)
94-
timer.C = clock
95-
return timer
96-
}
86+
newTimer := makeMockedTimer(clock)
9787

9888
var wg sync.WaitGroup
9989
wg.Add(1)
10090

101-
onTimeout := func() {
102-
wg.Done()
103-
}
104-
105-
roChan := make(<-chan struct{})
91+
preemptChan := make(<-chan struct{})
10692

107-
ts := NewTimeoutScheduler(onTimeout, roChan, newTimer)
93+
ts := NewTimeoutScheduler(wg.Done, preemptChan)
94+
ts.newTimer = newTimer
10895

10996
ts.RegisterTimeout(time.Hour) // First timeout is registered
11097
ts.RegisterTimeout(time.Hour) // Second should not
@@ -115,3 +102,15 @@ func TestTimeoutSchedulerConcurrentRegister(_ *testing.T) {
115102

116103
wg.Wait()
117104
}
105+
106+
func makeMockedTimer(clock chan time.Time) func(_ time.Duration) *time.Timer {
107+
newTimer := func(_ time.Duration) *time.Timer {
108+
// We use a duration of 0 to not leave a lingering timer
109+
// after the test finishes.
110+
// Then we replace the time channel to have control over the timer.
111+
timer := time.NewTimer(0)
112+
timer.C = clock
113+
return timer
114+
}
115+
return newTimer
116+
}

snow/engine/snowman/bootstrap/bootstrapper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func New(config Config, onFinished func(ctx context.Context, lastReqID uint32) e
149149
bs.Config.Ctx.Log.Warn("Encountered error during bootstrapping: %w", zap.Error(err))
150150
}
151151
}
152-
bs.TimeoutRegistrar = common.NewTimeoutScheduler(timeout, config.BootstrapTracker.AllBootstrapped(), time.NewTimer)
152+
bs.TimeoutRegistrar = common.NewTimeoutScheduler(timeout, config.BootstrapTracker.AllBootstrapped())
153153

154154
return bs, err
155155
}

0 commit comments

Comments
 (0)