Skip to content
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

fix panic in PeriodicJobEnqueuer #73

Merged
merged 2 commits into from
Nov 26, 2023
Merged

Conversation

bgentry
Copy link
Contributor

@bgentry bgentry commented Nov 26, 2023

The previous version of this code could sometimes provide a non-positive interval for Ticker.Reset, which causes it to panic. This could happen if a job was already overdue to run when timeUntilNextRun() was called, or as time progressed throughout its execution.

There are two fixes here:

  1. In timeUntilNextRun(), only evaluate the current time once and use that throughout so we can be sure we're not skipping safety checks.
  2. Switch the PeriodicJobEnqueuer loop to use a time.Timer instead of a time.Ticker since we intend to reset it on every iteration anyway.

Step (2) was tricky as it always is when reusing time.Timer in Go [1] thanks to its poor design, but I believe I've done that in a safe way here. The reusable timer is now created with 0 duration, ensuring that it will fire quickly, and then the next line always blocks until it fires. This introduces a minimal delay, but it's done to ensure that the first part of the loop (resetting the timer) can always succeed without needing to first try to stop the timer. The only other way the loop can repeat is if we've just received from the timer, so we are guaranteed to be able to safely reset it here.

@bgentry bgentry requested a review from brandur November 26, 2023 20:44
@bgentry bgentry force-pushed the bg-fix-periodic-enqueuer-panic branch from 3fc4029 to 3585375 Compare November 26, 2023 20:45
The previous version of this code could sometimes provide a non-positive
interval for Ticker.Reset, which causes it to panic. This could happen
if a job was already overdue to run when `timeUntilNextRun()` was
called, or as time progressed throughout its execution.

There are two fixes here:

1. In `timeUntilNextRun()`, only evaluate the current time once and use
   that throughout so we can be sure we're not skipping safety checks.
2. Switch the `PeriodicJobEnqueuer` loop to use a `time.Timer` instead
   of a `time.Ticker` since we intend to reset it on every iteration
   anyway.

Step (2) was tricky as it always is when reusing `time.Timer` in Go [1]
thanks to its poor design, but I believe I've done that in a safe way
here. The reusable timer is now created with 0 duration, ensuring that
it will fire quickly, and then the next line always blocks until it
fires. This introduces a minimal delay, but it's done to ensure that the
first part of the loop (resetting the timer) can always succeed without
needing to first try to stop the timer. The only other way the loop can
repeat is if we've just received from the timer, so we are guaranteed to
be able to safely reset it here.

[1]: https://blogtitle.github.io/go-advanced-concurrency-patterns-part-2-timers/
@bgentry bgentry force-pushed the bg-fix-periodic-enqueuer-panic branch from 3585375 to 593ae13 Compare November 26, 2023 20:50
Comment on lines +171 to +176
// Clean up timer resources. We know it has _not_ received from the
// timer since its last reset because that would have led us to the case
// above instead of here.
if !timerUntilNextRun.Stop() {
<-timerUntilNextRun.C
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this here to ensure we're stopping and draining the timer before returning from this loop.

@bgentry bgentry merged commit 965925c into master Nov 26, 2023
7 checks passed
@bgentry bgentry deleted the bg-fix-periodic-enqueuer-panic branch November 26, 2023 22:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants