Skip to content

Commit 24370be

Browse files
authored
[Fake clock] Make Stop / Reset return false if Timer stopped (#320)
* [Fake clock] Make Stop / Reset return false if Timer stopped In order to conform to the standard library Timer, Stop and Reset should return false if the Timer has already been stopped, which was not the case previously. We add unit tests to validate the new implementation. The updated TestFakeStop test case and the new TestFakeReset/reset_stopped_timer case fail with the old implementation. We also simplify the implementation of Stop and Rest in the following way: there is no need to check f.waiter.fired to determine whether a Timer has expired. For Timers, this flag is set atomically with the waiter being removed from the waiters list. As a result, we only check for absence of the waiter from the list, which either indicates that the Timer has been stopped or that it has already expired (fired). Signed-off-by: Antonin Bas <[email protected]> * Address review comments Removed unused fired field for waiters. Signed-off-by: Antonin Bas <[email protected]> --------- Signed-off-by: Antonin Bas <[email protected]>
1 parent 6fe5fd8 commit 24370be

File tree

2 files changed

+96
-15
lines changed

2 files changed

+96
-15
lines changed

clock/testing/fake_clock.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type fakeClockWaiter struct {
4848
stepInterval time.Duration
4949
skipIfBlocked bool
5050
destChan chan time.Time
51-
fired bool
5251
afterFunc func()
5352
}
5453

@@ -198,12 +197,10 @@ func (f *FakeClock) setTimeLocked(t time.Time) {
198197
if w.skipIfBlocked {
199198
select {
200199
case w.destChan <- t:
201-
w.fired = true
202200
default:
203201
}
204202
} else {
205203
w.destChan <- t
206-
w.fired = true
207204
}
208205

209206
if w.afterFunc != nil {
@@ -305,44 +302,48 @@ func (f *fakeTimer) C() <-chan time.Time {
305302
return f.waiter.destChan
306303
}
307304

308-
// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise.
305+
// Stop prevents the Timer from firing. It returns true if the call stops the
306+
// timer, false if the timer has already expired or been stopped.
309307
func (f *fakeTimer) Stop() bool {
310308
f.fakeClock.lock.Lock()
311309
defer f.fakeClock.lock.Unlock()
312310

311+
active := false
313312
newWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters))
314313
for i := range f.fakeClock.waiters {
315314
w := f.fakeClock.waiters[i]
316315
if w != &f.waiter {
317316
newWaiters = append(newWaiters, w)
317+
continue
318318
}
319+
// If timer is found, it has not been fired yet.
320+
active = true
319321
}
320322

321323
f.fakeClock.waiters = newWaiters
322324

323-
return !f.waiter.fired
325+
return active
324326
}
325327

326-
// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet
327-
// fired, or false otherwise.
328+
// Reset changes the timer to expire after duration d. It returns true if the
329+
// timer had been active, false if the timer had expired or been stopped.
328330
func (f *fakeTimer) Reset(d time.Duration) bool {
329331
f.fakeClock.lock.Lock()
330332
defer f.fakeClock.lock.Unlock()
331333

332-
active := !f.waiter.fired
334+
active := false
333335

334-
f.waiter.fired = false
335336
f.waiter.targetTime = f.fakeClock.time.Add(d)
336337

337-
var isWaiting bool
338338
for i := range f.fakeClock.waiters {
339339
w := f.fakeClock.waiters[i]
340340
if w == &f.waiter {
341-
isWaiting = true
341+
// If timer is found, it has not been fired yet.
342+
active = true
342343
break
343344
}
344345
}
345-
if !isWaiting {
346+
if !active {
346347
f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter)
347348
}
348349

clock/testing/fake_clock_test.go

+83-3
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,19 @@ func TestFakeStop(t *testing.T) {
281281
if !tc.HasWaiters() {
282282
t.Errorf("expected a waiter to be present, but it is not")
283283
}
284-
timer.Stop()
284+
if !timer.Stop() {
285+
t.Errorf("stop should return true as we are stopping an unexpired timer")
286+
}
285287
if tc.HasWaiters() {
286288
t.Errorf("expected existing waiter to be cleaned up, but it is still present")
287289
}
290+
if timer.Stop() {
291+
t.Errorf("stop should return false as the timer has already been stopped")
292+
}
288293
}
289294

290295
// This tests the pattern documented in the go docs here: https://golang.org/pkg/time/#Timer.Stop
291-
// This pattern is required to safely reset a timer, so should be common.
296+
// This pattern is required to safely reset a timer prior to Go 1.23, so should be common.
292297
// This also tests resetting the timer
293298
func TestFakeStopDrain(t *testing.T) {
294299
start := time.Time{}
@@ -303,7 +308,9 @@ func TestFakeStopDrain(t *testing.T) {
303308
t.Errorf("timer should have ticked after 1 second, got %v", readTime)
304309
}
305310

306-
timer.Reset(time.Second)
311+
if timer.Reset(time.Second) {
312+
t.Errorf("reset should return false as the timer had expired")
313+
}
307314
if !tc.HasWaiters() {
308315
t.Errorf("expected a waiter to be present, but it is not")
309316
}
@@ -318,6 +325,79 @@ func TestFakeStopDrain(t *testing.T) {
318325
}
319326
}
320327

328+
func TestFakeReset(t *testing.T) {
329+
start := time.Now()
330+
t.Run("reset active timer", func(t *testing.T) {
331+
tc := NewFakeClock(start)
332+
timer := tc.NewTimer(time.Second)
333+
if !tc.HasWaiters() {
334+
t.Errorf("expected a waiter to be present, but it is not")
335+
}
336+
tc.Step(999 * time.Millisecond)
337+
if !tc.HasWaiters() {
338+
t.Errorf("expected a waiter to be present, but it is not")
339+
}
340+
if !timer.Reset(time.Second) {
341+
t.Errorf("reset should return true as the timer is active")
342+
}
343+
tc.Step(time.Millisecond)
344+
if !tc.HasWaiters() {
345+
t.Errorf("expected a waiter to be present, but it is not")
346+
}
347+
tc.Step(999 * time.Millisecond)
348+
if tc.HasWaiters() {
349+
t.Errorf("expected existing waiter to be cleaned up, but it is still present")
350+
}
351+
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(1999 * time.Millisecond)) {
352+
t.Errorf("timer should have ticked after reset + 1 second, got %v", readTime)
353+
}
354+
})
355+
356+
t.Run("reset expired timer", func(t *testing.T) {
357+
tc := NewFakeClock(start)
358+
timer := tc.NewTimer(time.Second)
359+
if !tc.HasWaiters() {
360+
t.Errorf("expected a waiter to be present, but it is not")
361+
}
362+
tc.Step(time.Second)
363+
if tc.HasWaiters() {
364+
t.Errorf("expected existing waiter to be cleaned up, but it is still present")
365+
}
366+
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(time.Second)) {
367+
t.Errorf("timer should have ticked after 1 second, got %v", readTime)
368+
}
369+
if timer.Reset(time.Second) {
370+
t.Errorf("reset should return false as the timer had expired")
371+
}
372+
if !tc.HasWaiters() {
373+
t.Errorf("expected a waiter to be present, but it is not")
374+
}
375+
tc.Step(time.Second)
376+
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(2 * time.Second)) {
377+
t.Errorf("timer should have ticked again after reset + 1 more second, got %v", readTime)
378+
}
379+
})
380+
381+
t.Run("reset stopped timer", func(t *testing.T) {
382+
tc := NewFakeClock(start)
383+
timer := tc.NewTimer(time.Second)
384+
if !tc.HasWaiters() {
385+
t.Errorf("expected a waiter to be present, but it is not")
386+
}
387+
timer.Stop()
388+
if timer.Reset(time.Second) {
389+
t.Errorf("reset should return false as the timer had been stopped")
390+
}
391+
if !tc.HasWaiters() {
392+
t.Errorf("expected a waiter to be present, but it is not")
393+
}
394+
tc.Step(time.Second)
395+
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(time.Second)) {
396+
t.Errorf("timer should have ticked after reset + 1 second, got %v", readTime)
397+
}
398+
})
399+
}
400+
321401
func TestTimerNegative(t *testing.T) {
322402
tc := NewFakeClock(time.Now())
323403
timer := tc.NewTimer(-1 * time.Second)

0 commit comments

Comments
 (0)