*/ public array $backoff = [15, 45]; // Doit rester sous le timeout du superviseur Horizon (300 s), lui-même // sous retry_after (330 s, voir .env) : sinon un job encore en cours // serait redonné à un autre processus. public int $timeout = 280; public function __construct(private readonly LabSession $labSession) { $this->onQueue('sessions'); } public function handle(FabriqueStack $fabrique, HeatClient $heat): void { $session = $this->labSession->fresh(); // Expirée ou relancée autrement entre-temps : plus rien à construire. if ($session === null || $session->etat !== LabSession::ADMIS) { return; } $base = "lab-session-{$session->id}"; $fabrique->nettoyerEssais($base); $stack = $fabrique->fabriquer( DefinitionLab::depuis($session->lab->identifiant), "{$base}-e{$this->attempts()}", fn (string $nom, string $id) => $session->forceFill(['heat_stack_name' => $nom, 'heat_stack_id' => $id])->save(), ); if ($session->fresh()->etat !== LabSession::ADMIS) { $heat->supprimerStack($stack['nom'], $stack['id']); return; } $session->attribuerStack($stack); } public function failed(?Throwable $exception): void { $session = $this->labSession->fresh(); if ($session === null) { return; } $session->forceFill([ 'etat' => LabSession::ECHEC, 'raison_echec' => Str::limit($exception?->getMessage() ?? 'raison inconnue', 250), 'heat_stack_name' => null, 'heat_stack_id' => null, ])->save(); Log::error("CreerStackLab : session {$session->id} en échec après {$this->tries} essais", [ 'raison' => $exception?->getMessage(), ]); // Le dernier essai a normalement supprimé sa stack ; pas si son // processus a été tué (timeout) : on repasse derrière. try { app(FabriqueStack::class)->nettoyerEssais("lab-session-{$session->id}"); } catch (Throwable $e) { Log::error("CreerStackLab : nettoyage impossible pour la session {$session->id}", ['raison' => $e->getMessage()]); } } }