diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index f97ff853c..635032dfe 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2459,20 +2459,23 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); private function next(string $status) { queue_next_deployment($this->application); - // If the deployment is cancelled by the user, don't update the status - if ( - $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && - $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value - ) { - $this->application_deployment_queue->update([ - 'status' => $status, - ]); + + // Never allow changing status from FAILED or CANCELLED_BY_USER to anything else + if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value || + $this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) { + return; } - if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) { + + $this->application_deployment_queue->update([ + 'status' => $status, + ]); + + if ($status === ApplicationDeploymentStatus::FAILED->value) { $this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); return; } + if ($status === ApplicationDeploymentStatus::FINISHED->value) { if (! $this->only_this_server) { $this->deploy_to_additional_destinations(); diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index fd8f1cba2..2a9bea67a 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -5,6 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; use OpenApi\Attributes as OA; #[OA\Schema( @@ -101,17 +102,23 @@ class ApplicationDeploymentQueue extends Model 'hidden' => $hidden, 'batch' => 1, ]; - if ($this->logs) { - $previousLogs = json_decode($this->logs, associative: true, flags: JSON_THROW_ON_ERROR); - $newLogEntry['order'] = count($previousLogs) + 1; - $previousLogs[] = $newLogEntry; - $this->update([ - 'logs' => json_encode($previousLogs, flags: JSON_THROW_ON_ERROR), - ]); - } else { - $this->update([ - 'logs' => json_encode([$newLogEntry], flags: JSON_THROW_ON_ERROR), - ]); - } + + // Use a transaction to ensure atomicity + DB::transaction(function () use ($newLogEntry) { + // Reload the model to get the latest logs + $this->refresh(); + + if ($this->logs) { + $previousLogs = json_decode($this->logs, associative: true, flags: JSON_THROW_ON_ERROR); + $newLogEntry['order'] = count($previousLogs) + 1; + $previousLogs[] = $newLogEntry; + $this->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR); + } else { + $this->logs = json_encode([$newLogEntry], flags: JSON_THROW_ON_ERROR); + } + + // Save without triggering events to prevent potential race conditions + $this->saveQuietly(); + }); } }