From cd37431abadfa17462ffeb9bb15a8859ada487a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 17:14:08 +0200 Subject: [PATCH] fix(geo-backfill): in-process singleton guard for the worker handler pg-boss already coalesces concurrent cron ticks across worker containers via the shared queue lease, but a single container that takes longer than the cron interval can pick up two jobs back-to-back when the second tick fires while the first pass is still running. A module-local geoBackfillRunning flag fans the second invocation out as a no-op log line instead of stacking two concurrent passes inside the same Node process. The next cron tick after the in-flight pass completes catches up the work the skipped pass would have done. --- src/lib/jobs/reminder-worker.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/jobs/reminder-worker.ts b/src/lib/jobs/reminder-worker.ts index 927372f69..b5ab29d8a 100644 --- a/src/lib/jobs/reminder-worker.ts +++ b/src/lib/jobs/reminder-worker.ts @@ -1188,10 +1188,32 @@ async function handleFeedbackAggregator( * provider unreachable) and re-resolves them through the now-bundled * resolver chain. The helper is idempotent and capped per pass so * the hourly cadence cannot starve a live login spike. + * + * v1.4.38 — in-process singleton guard. pg-boss already coalesces + * concurrent cron ticks across multiple worker containers via the + * shared queue lease, but a single container that takes longer than + * one cron interval can pick up two jobs back-to-back when the + * second tick fires while the first pass is still running. The + * in-process `geoBackfillRunning` flag fans the second invocation + * out as a no-op log line instead of stacking two concurrent passes + * inside the same Node process — the next cron tick after the first + * completes will catch up the work the skipped pass would have done. */ +let geoBackfillRunning = false; async function handleGeoBackfill(jobs: Job[]) { void jobs; await withBackgroundEvent("job.geo_backfill", async (evt) => { + if (geoBackfillRunning) { + // Earlier pass still in flight; skip this tick. Idempotent + the + // next tick after the in-flight pass completes will pick up + // anything we miss here. + evt.addWarning( + "geo-backfill skipped — earlier pass still in flight inside the same worker process", + ); + evt.addMeta("geo_backfill_skipped", true); + return; + } + geoBackfillRunning = true; const p = getWorkerPrisma(); try { const summary = await runGeoBackfill(p); @@ -1204,6 +1226,8 @@ async function handleGeoBackfill(jobs: Job[]) { // log and move on so a one-off resolver hiccup does not poison // the queue and block the next pass. evt.addWarning(`geo-backfill failed: ${err}`); + } finally { + geoBackfillRunning = false; } }); }