fix: tolerate late heartbeats so drifting timers don't skip executions by merencia · Pull Request #534 · node-cron/node-cron · GitHub
Skip to content

fix: tolerate late heartbeats so drifting timers don't skip executions#534

Merged
merencia merged 1 commit into
mainfrom
fix/missed-execution-tolerance
Jun 16, 2026
Merged

fix: tolerate late heartbeats so drifting timers don't skip executions#534
merencia merged 1 commit into
mainfrom
fix/missed-execution-tolerance

Conversation

@merencia

@merencia merencia commented Jun 16, 2026

Copy link
Copy Markdown
Member

Closes #485

Problem

Long setTimeout delays drift (OS sleep, GC, throttling, clock skew). On daily/weekly schedules a heartbeat could wake a few seconds late, fail the exact-second match(), skip the execution and emit a spurious missed execution warning. The miss rate scaled with timer duration, so daily/weekly jobs were hit hardest.

Reported in #485 (with a strong inspector-level diagnosis showing the timer fires but the callback is skipped, miss rate correlating with timer duration) and the WSL2 clock-drift reports in #475.

Fix

New configurable option missedExecutionTolerance (ms, default 1000): a heartbeat that wakes within the tolerance of the slot it was armed for runs that slot instead of reporting it missed.

The tolerance is always bounded by the gap to the next slot, so:

  • a late run can never bleed into the following slot, and
  • a tolerance larger than the cron interval can never run a slot twice.

The run / miss / wait decision is extracted into a pure planBeat function with exhaustive scenario tests (on-time, late-within-tolerance, late-beyond-tolerance, early fire, the 1s-cron cap, hourly recovery, the superseded-slot boundary, long-block catch-up). The runner now drives off the slot it armed for rather than re-matching the late wall clock.

The existing "missed execution" suppression (suppressMissedWarning and the auto-suppress when an execution:missed listener is attached) is unchanged.

Notes for #475 (WSL2)

That one is environmental: the WSL2 guest clock desyncs from the Windows host (microsoft/WSL#10006). The grace window softens minor drift, but a large clock jump is still a genuine miss. The confirmed user fix is an NTP/hwclock resync.

Long setTimeout delays drift (OS sleep, GC, throttling, clock skew). On
daily/weekly schedules a heartbeat could wake a few seconds late, fail the
exact-second match, skip the execution and emit a spurious "missed execution"
warning. The miss rate scaled with timer duration, so daily/weekly jobs were
hit hardest (reported in #485, and the WSL2 clock-drift reports in #475).

Introduce a configurable `missedExecutionTolerance` (ms, default 1000): a
heartbeat that wakes within the tolerance of the slot it was armed for runs
that slot instead of reporting it missed. The tolerance is always bounded by
the gap to the next slot, so a late run can never bleed into the following one
and a tolerance larger than the interval can never run a slot twice.

The run/miss/wait decision is extracted into a pure `planBeat` function with
exhaustive scenario tests; the runner now drives off the slot it armed for
rather than re-matching the (late) wall clock.
@merencia merencia changed the base branch from master to main June 16, 2026 19:28
@merencia merencia closed this Jun 16, 2026
@merencia merencia reopened this Jun 16, 2026
@merencia merencia merged commit e519d6c into main Jun 16, 2026
3 checks passed
@merencia merencia deleted the fix/missed-execution-tolerance branch June 16, 2026 19:41
@merencia merencia mentioned this pull request Jun 17, 2026
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.

Opt out "Possible blocking IO or high CPU user..." check

1 participant