Conversation
… dates Agent-Logs-Url: https://github.com/Hexagon/croner/sessions/e95ad1b0-1f4e-41a1-98a6-8a308ac6f36c Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes a cross-runtime scheduling bug where very large negative setTimeout delays (e.g., fire-once jobs in the far past with allowPast: true) can be coerced into large positive delays in Deno, preventing the job from firing.
Changes:
- Clamp negative
waitMsvalues to0inCron.schedule()after applying the existingmaxDelaycap. - Add an explanatory comment documenting the Deno-specific overflow/coercion behavior and its impact.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Clamp negative delays to 0 - some runtimes (e.g. Deno) treat large negative values | ||
| // as large positive delays due to 32-bit integer overflow, causing jobs with allowPast:true | ||
| // and a far-past date to never fire. | ||
| if (waitMs < 0) { | ||
| waitMs = 0; | ||
| } |
There was a problem hiding this comment.
Clamping negative waitMs to 0 can create a tight rescheduling loop when the job is paused and the target time is already in the past (e.g. paused: true + allowPast: true + far-past once date). In that case _checkTrigger never fires because paused, but schedule() keeps computing a negative delay and scheduling setTimeout(..., 0) repeatedly, which can cause high CPU usage. Consider adding a small backoff when paused/overdue (or changing pause/resume behavior to avoid rescheduling while paused).
|
@copilot apply changes based on the comments in this thread . Keep it safe. Minimal changes. |
…ling loop Agent-Logs-Url: https://github.com/Hexagon/croner/sessions/d4fd67cd-7705-4339-a1a7-fc03a7a191e7 Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com>
Applied in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // as large positive delays due to 32-bit integer overflow, causing jobs with allowPast:true | ||
| // and a far-past date to never fire. Use a backoff when paused to avoid a tight loop. | ||
| if (waitMs < 0) { | ||
| waitMs = this._states.paused ? 1000 : 0; | ||
| } |
There was a problem hiding this comment.
The new paused-specific branch (waitMs = this._states.paused ? 1000 : 0) changes runtime behavior but doesn’t appear to be covered by existing tests. Consider adding a regression test for a fire-once job with allowPast: true and a far-past date created with { paused: true }, then resume() it and assert it fires within a bounded time (and not after an overflow-sized delay).
There was a problem hiding this comment.
Added regression test in 9d31329. The test creates a fire-once job with allowPast: true, paused: true, and a far-past date (2020-01-01), verifies it doesn't fire while paused, then calls resume() and asserts it fires within 2 seconds. All 552 tests pass.
…ith resume Agent-Logs-Url: https://github.com/Hexagon/croner/sessions/8e0fb2f9-4e69-4243-9a40-4f5434f51569 Co-authored-by: Hexagon <419737+Hexagon@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
When scheduling a fire-once job with
allowPast: trueand a far-past date,msToNext()returns a large negative value. Deno coerces this to an unsigned 32-bit integer before passing it to the OS timer, turning e.g.-197331393963ms into~237101653ms (~2.7 days), so the job silently never fires.Change
src/croner.ts—schedule(): ClampwaitMsto0when negative, after the existingmaxDelaycap. When the job is paused, use a 1 s backoff instead of0to prevent a tightsetTimeout(0)→_checkTrigger→schedule()rescheduling loop.test/croner.test.ts: Added a regression test that creates a fire-once job withallowPast: true,paused: true, and a far-past date, then callsresume()and asserts it fires within a bounded time._checkTriggerstill evaluatesnow >= targetbefore firing, so clamping to0means "check immediately" — not "fire unconditionally". Node.js and Bun were unaffected because they perform this clamping natively; Deno does not.