Skip to content

Commit 7aab9b9

Browse files
committed
fix(run-engine): re-arm the park deadline when a parked run is not due yet
The external-deployment park deadline is armed once, when the run is first parked, from max(now, delayUntil) + deadline. Debounce can push delayUntil out afterwards: rescheduleDelayedRun reschedules enqueueDelayedRun:<id>, and the redis-worker reschedule is an update-only ZADD, so expireParkedExternalDeploymentRun:<id> is never re-armed. Repeat triggers on one debounce key therefore walk delayUntil past a deadline that no longer moves, and the run is expired with EXTERNAL_DEPLOYMENT_NOT_FOUND before it was ever due to start. Reproduced against a local instance: a run due at 14:01:37 was expired at 13:57:02, blaming a missing deployment. The expiry job already loads delayUntil, so it now re-arms from the current value and returns instead of expiring a run that is not due. Putting the guard here rather than in the debounce path covers every caller that moves delayUntil, and it stays bounded by the debounce max-duration contract.
1 parent 0110494 commit 7aab9b9

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

internal-packages/run-engine/src/engine/systems/pendingVersionSystem.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,22 @@ export class PendingVersionSystem {
470470
);
471471
}
472472

473+
if (run.delayUntil && run.delayUntil > new Date()) {
474+
this.$.logger.info(
475+
"expireParkedExternalDeploymentRun: run is not due yet, re-arming the park deadline",
476+
{ runId, externalDeploymentId, delayUntil: run.delayUntil }
477+
);
478+
479+
await this.scheduleExternalDeploymentParkDeadline({
480+
runId,
481+
externalDeploymentId,
482+
ttl: run.ttl,
483+
delayUntil: run.delayUntil,
484+
});
485+
486+
return;
487+
}
488+
473489
const error: TaskRunError = {
474490
type: "STRING_ERROR",
475491
raw: `Run expired because no deployment with external id '${externalDeploymentId}' became available`,

internal-packages/run-engine/src/engine/tests/externalDeploymentParking.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,66 @@ describe("RunEngine external deployment parking", () => {
566566
}
567567
);
568568

569+
containerTest(
570+
"the parking deadline re-arms instead of expiring a run whose delay was pushed past it",
571+
async ({ prisma, redisOptions }) => {
572+
const authenticatedEnvironment = await setupAuthenticatedEnvironment(prisma, "PRODUCTION");
573+
const engine = createEngine(prisma, redisOptions);
574+
575+
try {
576+
const taskIdentifier = "test-task";
577+
578+
const run = await engine.trigger(
579+
{
580+
number: 1,
581+
friendlyId: "run_1234",
582+
environment: authenticatedEnvironment,
583+
taskIdentifier,
584+
payload: "{}",
585+
payloadType: "application/json",
586+
context: {},
587+
traceContext: {},
588+
traceId: "t1234",
589+
spanId: "s1234",
590+
queue: `task/${taskIdentifier}`,
591+
isTest: false,
592+
tags: [],
593+
delayUntil: new Date(Date.now() + 60 * 1000),
594+
annotations: {
595+
triggerSource: "sdk",
596+
triggerAction: "trigger",
597+
rootTriggerSource: "sdk",
598+
externalDeploymentId: "commit-pushed",
599+
},
600+
parkedOnExternalDeploymentId: "commit-pushed",
601+
},
602+
prisma
603+
);
604+
605+
// Stand in for a debounce push: the run's delay moves out, but nothing re-arms the
606+
// deadline that was computed when the run was first parked.
607+
const pushedDelayUntil = new Date(Date.now() + 60 * 60 * 1000);
608+
await prisma.taskRun.update({
609+
where: { id: run.id },
610+
data: { delayUntil: pushedDelayUntil },
611+
});
612+
613+
await engine.pendingVersionSystem.expireParkedExternalDeploymentRun({
614+
runId: run.id,
615+
externalDeploymentId: "commit-pushed",
616+
});
617+
618+
const stillParked = await prisma.taskRun.findFirstOrThrow({ where: { id: run.id } });
619+
620+
expect(stillParked.status).toBe("PENDING_VERSION");
621+
expect(stillParked.statusReason).toBe("EXTERNAL_DEPLOYMENT_PENDING");
622+
expect(stillParked.expiredAt).toBeNull();
623+
} finally {
624+
await engine.quit();
625+
}
626+
}
627+
);
628+
569629
containerTest(
570630
"the parking deadline expires a run whose deployment never arrived",
571631
async ({ prisma, redisOptions }) => {

0 commit comments

Comments
 (0)