Fix scheduler thread stuck after system clock jumps backward (#1508)#2929
Merged
Conversation
When the system clock jumps backward, the trigger-wait loop in QuartzSchedulerThread could block for the duration of the clock jump (potentially hours) because schedulingChangeSignal.WaitAsync used timeUntilTrigger as an unbounded timeout. Cap the wait at idleWaitTime (30s default) so the thread wakes up periodically and recomputes from the current clock. Under normal operation the cap never activates since triggers are acquired at most idleWaitTime into the future. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
Caps the scheduler thread’s trigger-wait timeout to ensure QuartzSchedulerThread reliably recovers and resumes firing triggers after a system clock jump backward (fix for #1508), and adds a unit test intended to validate the regression.
Changes:
- Cap
schedulingChangeSignal.WaitAsync(...)timeout toidleWaitTimewhile waiting for the next trigger fire time. - Add
SystemTimeClockJumpTestto exercise recovery behavior after a backward clock jump.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/Quartz/Core/QuartzSchedulerThread.cs |
Bounds the trigger wait duration to avoid extended sleeps when the system clock moves backward. |
src/Quartz.Tests.Unit/Core/SystemTimeClockJumpTest.cs |
Adds a regression test for the backward clock jump scenario (needs adjustment to reliably exercise acquisition/wait path). |
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
QuartzSchedulerThreadatidleWaitTime(30s default) so the thread recovers from system clock backward jumps within one idle-wait cycleidleWaitTimeinto the future — zero performance impactFixes #1508
Details
When the system clock jumps backward (NTP correction, manual change, VM migration),
timeUntilTriggerin the trigger-wait loop becomes arbitrarily large (hours/days). TheschedulingChangeSignal.WaitAsync(timeUntilTrigger, ...)call would block for the entire duration with nothing to signal it, causing triggers to stop firing.The outer while loop already recomputes
timeUntilTriggerfrom the current clock after each wait, so capping the wait is safe and sufficient.Test plan
SystemTimeClockJumpTestvalidates trigger fires after clock-backward jump usingSystemTime.UtcNowmockingHighCpuSchedulerTestandShutdownTriggerReleaseTestpass (no regression)🤖 Generated with Claude Code