Skip to content
This repository has been archived by the owner on May 18, 2023. It is now read-only.

Commit

Permalink
Address re-entrancy of Tick
Browse files Browse the repository at this point in the history
This defers execution of the ticked function until after the lock has
been released, allowing it to make other calls without a deadlock.
  • Loading branch information
djmitche committed Oct 25, 2021
1 parent f7a1d13 commit 0b9476f
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 2 deletions.
8 changes: 6 additions & 2 deletions clock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions clock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...) }

0 comments on commit 0b9476f

Please sign in to comment.