From c7fb1ce56d2b37df5307ca4b9f2c0e053a2d377e Mon Sep 17 00:00:00 2001 From: nexxo Date: Sat, 25 Jul 2026 11:09:24 +0200 Subject: [PATCH] fix(engine): dispatch run continuation after releasing the lock Prevents a second worker from consuming the follow-up job and bailing on the still-held run lock, which would stall the run until the next scheduler tick. Co-Authored-By: Claude Opus 4.8 --- app/Provisioning/RunRunner.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/Provisioning/RunRunner.php b/app/Provisioning/RunRunner.php index 60f34b8..1886bad 100644 --- a/app/Provisioning/RunRunner.php +++ b/app/Provisioning/RunRunner.php @@ -18,8 +18,12 @@ class RunRunner { public function __construct(private PipelineRegistry $registry) {} + private ?string $pendingContinuation = null; + public function advance(ProvisioningRun $run): void { + $this->pendingContinuation = null; + // Lock must outlive the longest step so a duplicate job (dispatched by // the minutely tick while this one still runs) can't execute concurrently. $lock = Cache::lock('run:'.$run->uuid, 2100); @@ -33,6 +37,13 @@ class RunRunner } finally { $lock->release(); } + + // Dispatch the continuation only AFTER releasing the lock, so a second + // worker can't consume it and then bail on the still-held lock. + if ($this->pendingContinuation !== null) { + AdvanceRunJob::dispatch($this->pendingContinuation); + $this->pendingContinuation = null; + } } private function runLocked(ProvisioningRun $run): void @@ -116,7 +127,7 @@ class RunRunner $run->save(); $this->record($run, $step->key(), 'advanced', null); - AdvanceRunJob::dispatch($run->uuid); + $this->pendingContinuation = $run->uuid; // dispatched after the lock releases } private function onRetry(ProvisioningRun $run, ProvisioningStep $step, StepResult $result): void