fix(cron): prevent every schedule from firing in infinite loop

When anchorMs is not provided (always in production), the schedule
computed nextRunAtMs as nowMs, causing jobs to fire immediately and
repeatedly instead of at the configured interval.

- Change nowMs <= anchor to nowMs < anchor to prevent early return
- Add Math.max(1, ...) to ensure steps is always at least 1
- Add test for anchorMs not provided case
This commit is contained in:
James Groat
2026-01-01 17:27:43 -07:00
parent 9180cbe821
commit 7154bc6857
2 changed files with 10 additions and 2 deletions

View File

@@ -23,4 +23,12 @@ describe("cron schedule", () => {
);
expect(next).toBe(anchor + 30_000);
});
it("computes next run for every schedule when anchorMs is not provided", () => {
const now = Date.parse("2025-12-13T00:00:00.000Z");
const next = computeNextRunAtMs({ kind: "every", everyMs: 30_000 }, now);
// Should return nowMs + everyMs, not nowMs (which would cause infinite loop)
expect(next).toBe(now + 30_000);
});
});

View File

@@ -12,9 +12,9 @@ export function computeNextRunAtMs(
if (schedule.kind === "every") {
const everyMs = Math.max(1, Math.floor(schedule.everyMs));
const anchor = Math.max(0, Math.floor(schedule.anchorMs ?? nowMs));
if (nowMs <= anchor) return anchor;
if (nowMs < anchor) return anchor;
const elapsed = nowMs - anchor;
const steps = Math.floor((elapsed + everyMs - 1) / everyMs);
const steps = Math.max(1, Math.floor((elapsed + everyMs - 1) / everyMs));
return anchor + steps * everyMs;
}