fix: prevent cron jobs from being skipped when timer fires#9573
fix: prevent cron jobs from being skipped when timer fires#9573lihn-ai-engineer wants to merge 1 commit intoopenclaw:mainfrom
Conversation
When the cron timer fires, ensureLoaded() was calling recomputeNextRuns() which recalculates nextRunAtMs for all jobs based on current time. This caused jobs scheduled for 'now' to have their nextRunAtMs pushed to 'now + interval', making them appear not due and skipping execution. Remove recomputeNextRuns from ensureLoaded since it's already called in start() which is sufficient for initialization. Fixes openclaw#9542
| state.storeLoadedAtMs = state.deps.nowMs(); | ||
| state.storeFileMtimeMs = fileMtimeMs; | ||
|
|
||
| // Recompute next runs after loading to ensure accuracy | ||
| recomputeNextRuns(state); | ||
| // NOTE: recomputeNextRuns removed here - it was causing jobs to be skipped | ||
| // when timer fires because nextRunAtMs gets recalculated to future time. | ||
| // recomputeNextRuns is called in start() which is sufficient. | ||
|
|
There was a problem hiding this comment.
Force reload can miss due jobs
ensureLoaded() is used on timer ticks with { forceReload: true } (src/cron/service/timer.ts:40). After this change, a force reload no longer recomputes job.state.nextRunAtMs, so if the store file is edited externally (or comes from an older/hand-edited format missing state.nextRunAtMs), runDueJobs() (src/cron/service/timer.ts:55-64) will see nextRunAtMs as undefined/stale and will never consider the job due until the service is restarted (since recomputeNextRuns() only runs in start()).
The fix that avoids recomputing on every load still needs a way to recompute after a force reload / after loading a store that may not have nextRunAtMs populated.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/service/store.ts
Line: 254:260
Comment:
**Force reload can miss due jobs**
`ensureLoaded()` is used on timer ticks with `{ forceReload: true }` (`src/cron/service/timer.ts:40`). After this change, a force reload no longer recomputes `job.state.nextRunAtMs`, so if the store file is edited externally (or comes from an older/hand-edited format missing `state.nextRunAtMs`), `runDueJobs()` (`src/cron/service/timer.ts:55-64`) will see `nextRunAtMs` as `undefined`/stale and will never consider the job due until the service is restarted (since `recomputeNextRuns()` only runs in `start()`).
The fix that avoids recomputing on *every* load still needs a way to recompute after a force reload / after loading a store that may not have `nextRunAtMs` populated.
How can I resolve this? If you propose a fix, please make it concise.|
found a different fix here #9393 |
|
Closing: Superseded by #9393 |
When the cron timer fires,
ensureLoaded()was callingrecomputeNextRuns()which recalculatesnextRunAtMsfor all jobs based on current time. This caused jobs scheduled for 'now' to have theirnextRunAtMspushed to 'now + interval', making them appear not due and skipping execution.Root cause
In
store.ts,ensureLoaded()calledrecomputeNextRuns(state)on every load. When the timer fires andrunDueJobs()is called, it eventually triggersensureLoaded(), which then recalculates all job schedules. Jobs that were due at that exact moment get their next run time pushed forward.Fix
Remove
recomputeNextRunsfromensureLoaded()since it's already called instart()which is sufficient for initialization.Fixes #9542
Greptile Overview
Greptile Summary
This PR removes the
recomputeNextRuns(state)call fromensureLoaded()to prevent a timer tick from advancing jobs that are due “now” into the future (which made them appear not due and skip execution). The recomputation is left instart()for initial scheduler initialization.However,
onTimer()performsensureLoaded(state, { forceReload: true })beforerunDueJobs(). WithrecomputeNextRuns()removed fromensureLoaded(), a force reload no longer guaranteesnextRunAtMsis populated/up-to-date after re-reading the store file, which can cause jobs to be skipped if the file was edited externally or lacksstate.nextRunAtMs.Confidence Score: 3/5
ensureLoaded(..., {forceReload:true})is used on timer ticks and now won’t refreshnextRunAtMsfrom the loaded store, so due detection can break when the store content doesn’t already contain correctnextRunAtMsvalues.(2/5) Greptile learns from your feedback when you react with thumbs up/down!