isReapplyable($instance)) { return null; } // The lock closes the window between asking whether a run exists and // creating one. Two requests can reach this in the same instant — the // customer's own "check now" and the nightly command are the obvious // pair — and two runs against one machine would write the same router // twice and race each other's occ calls. Nobody waits for the lock: a // re-apply that lost the race has nothing to add, because the run that // won reads the same domain state it would have. $lock = Cache::lock('instance-address:'.$instance->uuid, 30); if (! $lock->get()) { return null; } try { return $this->start($instance); } finally { $lock->release(); } } private function start(Instance $instance): ?ProvisioningRun { $order = $instance->order; // A run already under way applies whatever the domain state says when // it gets to the step, which is this run's state or newer. Checked // against the ORDER, because that is the subject both pipelines share: // starting an address run beside an unfinished customer run would have // two runs writing one router and one Nextcloud config. if ($this->hasRunInFlight($order)) { return null; } $run = ProvisioningRun::create([ 'subject_type' => Order::class, 'subject_id' => $order->id, 'pipeline' => 'address', 'status' => ProvisioningRun::STATUS_PENDING, 'current_step' => 0, // Everything the two steps read. instance_id is how CustomerStep // finds the machine; node and vmid are how it reaches inside it. 'context' => [ 'instance_id' => $instance->id, 'host_id' => $instance->host_id, 'node' => $instance->host?->node, 'vmid' => $instance->vmid, 'subdomain' => $instance->subdomain, ], ]); AdvanceRunJob::dispatch($run->uuid); Log::info('Re-applying an instance address.', [ 'instance' => $instance->uuid, 'domain' => $instance->custom_domain, 'verified' => $instance->domainIsVerified(), 'run' => $run->uuid, ]); return $run; } /** * Is there a machine here whose address can be re-applied at all? * * The question is not this action's alone — a plan change asks exactly the * same one before it starts a run against a live machine — so the rule sits * on the model and both read it there. See Instance::hasLiveMachine(). */ private function isReapplyable(Instance $instance): bool { return $instance->hasLiveMachine(); } private function hasRunInFlight(Order $order): bool { return ProvisioningRun::query() ->where('subject_type', Order::class) ->where('subject_id', $order->id) ->whereIn('status', [ ProvisioningRun::STATUS_PENDING, ProvisioningRun::STATUS_RUNNING, ProvisioningRun::STATUS_WAITING, ProvisioningRun::STATUS_PAUSED, ]) ->exists(); } }