From 0b9476f9982663914d9bdb829375ec2aa9d0217a Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Mon, 25 Oct 2021 18:01:18 +0000 Subject: [PATCH] Address re-entrancy of Tick This defers execution of the ticked function until after the lock has been released, allowing it to make other calls without a deadlock. --- clock.go | 8 ++++++-- clock_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/clock.go b/clock.go index a6b56a8..da3c4fb 100644 --- a/clock.go +++ b/clock.go @@ -284,16 +284,20 @@ type internalTimer Timer func (t *internalTimer) Next() time.Time { return t.next } func (t *internalTimer) Tick(now time.Time) { + // a gosched() after ticking, to allow any consequences of the + // tick to complete + defer gosched() + t.mock.mu.Lock() if t.fn != nil { - t.fn() + // defer function execution until the lock is released, and + defer t.fn() } else { t.c <- now } t.mock.removeClockTimer((*internalTimer)(t)) t.stopped = true t.mock.mu.Unlock() - gosched() } // Ticker holds a channel that receives "ticks" at regular intervals. diff --git a/clock_test.go b/clock_test.go index e41bca2..f072b25 100644 --- a/clock_test.go +++ b/clock_test.go @@ -613,5 +613,20 @@ func ExampleMock_Timer() { // Count is 1 after 10 seconds } +func TestMock_ReentrantDeadlock(t *testing.T) { + mockedClock := NewMock() + timer20 := mockedClock.Timer(20 * time.Second) + go func() { + v := <-timer20.C + panic(fmt.Sprintf("timer should not have ticked: %v", v)) + }() + mockedClock.AfterFunc(10*time.Second, func() { + timer20.Stop() + }) + + mockedClock.Add(15 * time.Second) + mockedClock.Add(15 * time.Second) +} + func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) } func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) }