Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bug in AdvanceNext if next scheduled time is 0 #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
oliverdain opened this issue Dec 24, 2024 · 3 comments
Closed

Bug in AdvanceNext if next scheduled time is 0 #11

oliverdain opened this issue Dec 24, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@oliverdain
Copy link

oliverdain commented Dec 24, 2024

The following unit test fails with a panic:

func TestQuartz(t *testing.T) {
	clock := quartz.NewMock(t)
	doneChan := make(chan any)
	go func() {
		timer := clock.NewTimer(0 * time.Second)
		<- timer.C
		close(doneChan)
	}()
	newTimerTrap := clock.Trap().NewTimer()
	defer newTimerTrap.Close()
	clock.AdvanceNext()
	<- doneChan
	log.Infof("Done at %s", clock.Now().String())
}

The issue is that I'm setting a timer with duration 0 which is legal. However, if you look at AdvanceNext you see:

func (m *Mock) AdvanceNext() (time.Duration, AdvanceWaiter) {
	m.mu.Lock()
	m.tb.Helper()
	w := AdvanceWaiter{tb: m.tb, ch: make(chan struct{})}
	if m.nextTime.IsZero() {
		defer close(w.ch)
		defer m.mu.Unlock()
		m.tb.Error("cannot AdvanceNext because there are no timers or tickers running")
	}
	d := m.nextTime.Sub(m.cur)
	m.cur = m.nextTime
	go m.advanceLocked(w)
	return d, w
}

There's two bugs here I think:

  1. It checks if m.nextTime.IsZero but that doesn't necessarily mean there's nothing scheduled as it is legal to set a timer with a duration of 0
  2. If it does hit that condition it calls defer m.mu.Unlock but then it still calls advanceLocked which unlocks that mutex so you get a panic due to a double unlock of the mutex.

Granted it's a bit of an odd case to schedule a timer for 0 seconds in the future but I think there are valid use cases for it. For my use case a user puts a request for a function to be called into a channel. We read from that channel and schedule the callback. It is possible that by the time we read from the channel the time has expired so we should call right away. We could special case that code but as it's legal to schedule a timer with 0-delay with a real timer and that's less code and fewere if statements we simply schedule the timer to fire immediately.

@coder-labeler coder-labeler bot added the bug Something isn't working label Dec 24, 2024
@spikecurtis
Copy link
Collaborator

The issue of the double-unlock is a legit bug and was fixed in: #10.

Zero-duration timers are fine in Quartz, but they fire immediately:

quartz/mock.go

Line 91 in d4dbd83

if d <= 0 {

So, it is not fine to call AdvanceNext() after because there is nothing else scheduled.

Also, as an aside, in your example, I see that you set a trap for NewTimer().

  1. The trap is set after you start a go routine that calls NewTimer(). This will race and there is no guarantee that you will actually trap the call. You need to set the trap before you start the goroutine.
  2. Your code never calls Wait() on the trap. After setting the trap, you need to Wait() on it and then Release() the call in order for NewTimer() to return.

Closing because I think the actual bug is already resolved. Please reopen if you think there is still an issue with the latest main branch.

@oliverdain
Copy link
Author

Thanks for the prompt reply! You're correct about the NewTimer trap - that was left over from the "real unit test" that was failing when I simplified it to make it suitable for a bug report.

It does look like the bug is fixed on main but that hasn't been released. The latest release, the one go mod tidy pulls, is 0.1.2 which was released before the bug fix. Any chance you could push a new release with the fix?

@spikecurtis
Copy link
Collaborator

New release published!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants