Skip to content

Commit 063e454

Browse files
committed
fix(webapp): retirement removals fully settle before healing from a fresh read
Both key removals settle before anything else runs, so a sibling removal can never land after the heal and erase it; and the heal re-reads the row instead of trusting the findMany snapshot, so it re-syncs the engine to whatever a concurrent writer persisted rather than stale values.
1 parent 259d7eb commit 063e454

1 file changed

Lines changed: 18 additions & 16 deletions

File tree

‎apps/webapp/app/v3/services/createBackgroundWorker.server.ts‎

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,7 @@ async function retireStaleAnonymousConcurrencyLimitRows(
450450
name: { in: boundedIn(candidateNames) },
451451
OR: [{ concurrencyLimit: { not: null } }, { totalConcurrencyLimit: { not: null } }],
452452
},
453-
select: {
454-
id: true,
455-
name: true,
456-
updatedAt: true,
457-
paused: true,
458-
concurrencyLimit: true,
459-
totalConcurrencyLimit: true,
460-
},
453+
select: { id: true, name: true, updatedAt: true },
461454
});
462455
if (staleRows.length === 0) {
463456
return;
@@ -472,21 +465,30 @@ async function retireStaleAnonymousConcurrencyLimitRows(
472465
* sync governs; a racing limits-surface sync converges via its freshness
473466
* re-check against the nulled row. */
474467
for (const row of staleRows) {
475-
try {
476-
await Promise.all([
477-
removeQueueConcurrencyLimits(environment, row.name),
478-
removeQueueTotalConcurrencyLimits(environment, row.name),
479-
]);
480-
} catch (error) {
468+
/** Both removals settle before anything else runs, so no removal is still
469+
* in flight when the heal writes; on any failure the heal re-reads the row
470+
* fresh (a concurrent writer may have moved it since the findMany) and
471+
* re-syncs the engine to whatever is persisted now. */
472+
const removals = await Promise.allSettled([
473+
removeQueueConcurrencyLimits(environment, row.name),
474+
removeQueueTotalConcurrencyLimits(environment, row.name),
475+
]);
476+
const removalFailures = removals.filter((result) => result.status === "rejected");
477+
if (removalFailures.length > 0) {
481478
logger.error(
482479
"retireStaleAnonymousConcurrencyLimitRows: engine cleanup failed, healing and retrying next deploy",
483480
{
484481
environmentId: environment.id,
485482
queueName: row.name,
486-
error,
483+
errors: removalFailures.map((failure) =>
484+
String((failure as PromiseRejectedResult).reason)
485+
),
487486
}
488487
);
489-
await syncLimitRowEngineStateBestEffort(environment, row);
488+
const fresh = await prisma.taskQueue.findFirst({ where: { id: row.id } });
489+
if (fresh) {
490+
await syncLimitRowEngineStateBestEffort(environment, fresh);
491+
}
490492
continue;
491493
}
492494
const retired = await prisma.taskQueue.updateMany({

0 commit comments

Comments
 (0)