Repository navigation
fix(scheduler): run reconciliation cron in UTC with replica dedupe - #992
Merged
greatest0fallt1me merged 3 commits intoOct 19, 2026
Conversation
…lica dedupe, and strict env booleans
Why: The reconciliation scheduler only matched literal numbers or wildcards in server-local time, polled via setInterval without replica deduplication or startup wiring, and z.coerce.boolean() treated 'false'/'0' as true.
What: Add dependency-free 5-field UTC cron parser and slot-based Scheduler with injectable clock/timers, per-slot guard, deterministic jobId ('reconciliation:<slot ISO minute>'), scheduler_runs_total counter, initializeScheduler() startup call, and envBoolean() helper.
Verification: npm test -- tests/scheduler.test.ts tests/env.test.ts tests/check-env.test.ts --coverage --collectCoverageFrom='src/services/scheduler.ts' (38/38 passed, 100% lines / 97.61% branches, exit 0).
…lot tracking sets Why: Delays exceeding 2^31 - 1 ms (~24.85 days, e.g., monthly/yearly cron schedules) overflow Node's 32-bit timer and fire immediately at 1 ms, while unbounded slot sets grow over long uptime. What: Split long delays across MAX_TIMEOUT_MS hops with pre-slot wake check, bound executedSlots and seenJobIdsByQueue to 1024 entries, and document AND field semantics and BullMQ 3-segment jobId format. Verification: npm test -- tests/scheduler.test.ts tests/env.test.ts tests/check-env.test.ts --coverage --collectCoverageFrom='src/services/scheduler.ts' (38/38 passed, 99.48% lines / 96.59% branches, exit 0).
…terministicClock Why: DeterministicClock accepted delays > 2^31 - 1 ms without clamping to 1 ms, so the monthly schedule test passed even against un-clamped setTimeout calls. What: Record requested timer delays, emulate Node 32-bit timer overflow (> 2_147_483_647 ms -> 1 ms) in DeterministicClock, and assert recordedDelays <= MAX_TIMEOUT_MS. Verification: Confirmed test fails on cd54e5c and passes on HEAD (38/38 passed, 99.48% lines / 96.59% branches).
|
@lhphong09t2 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
Closes #982
Summary
Makes the reconciliation scheduler execute on UTC cron boundaries, deduplicate scheduled jobs across replicas, and parse boolean environment flags strictly.
Key Changes
src/services/scheduler.ts): Supports*,*/n,a-b,a-b/n, and comma lists across minute, hour, day-of-month, month, and day-of-week (7 -> 0Sunday normalization and logical AND semantics) evaluated strictly in UTC.src/services/scheduler.ts): Multi-hoparmTimerUntilSlot()clampssetTimeoutdelays toMAX_TIMEOUT_MS(2_147_483_647ms) with pre-slot wake checks for monthly/yearly schedules, and boundsexecutedSlots/seenJobIdsByQueueto1_024entries.src/services/scheduler.ts,src/index.ts,src/metrics/registry.ts): Enqueues reconciliation jobs with BullMQ-safe per-slotjobIdvalues (reconcile_<ISO-UTC-minute>with colons replaced by-) and wires scheduler lifecycle hooks into startup/shutdown and metrics.src/config/env-schema.ts,src/config/env.ts,docs/job-queue.md): Replacesz.coerce.boolean()withbooleanFromEnvso"false","0","no", and"off"evaluate tofalse.Verification & Notes
npm test -- tests/scheduler.test.ts tests/env.test.ts tests/check-env.test.ts --coverage --collectCoverageFrom="src/services/scheduler.ts"passes3/3suites (38/38tests;98.00%stmts,96.59%branches,99.48%lines onsrc/services/scheduler.ts).src/config/env.ts): On basemain,tests/env.test.tsfailed 4 assertions (JWT_TTL validation,error message, andwarning log) because an earlier merge replacedenvSchema.parsewithsafeParse+process.exit(1)and dropped thepinowarning whenJWT_TTL_SECONDSis within 10% ofWORKER_HEARTBEAT_SECONDS * 2.src/config/env.tsrestores throwingparsed.errorwhenNODE_ENV !== "test"and logging the 10% threshold warning while keepingNODE_ENV === "test"tolerance intact.npx eslint(0errors/warnings) and introduce0newnpx tsc --noEmitdiagnostics (280pre-existing on both95d73d06and9afcf906).