fix(site-runtime): refuse an enable while a deploy pipeline owns the instance; revert is_enabled only for an invalid name (review 2026-10-21 follow-up L3)

This commit is contained in:
Joseph Doherty
2026-10-22 09:48:55 -04:00
parent e60c316f30
commit d646f816a4
3 changed files with 93 additions and 9 deletions
@@ -1799,6 +1799,18 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// Warm-then-gate: see TryBufferDuringDeployWarm.
if (TryBufferDuringDeployWarm(instanceName, command, replyTo)) return;
// Review 2026-10-21 follow-up L3: a redeploy/delete past its warm still OWNS the instance
// (predecessor terminating, or awaiting its persistence/init join) and will create the
// actor itself. An enable racing it would collide with the terminating predecessor's
// actor name in CreateInstanceActor — refuse it plainly instead.
if (InstanceIsOwnedByADeployPipeline(instanceName))
{
replyTo.Tell(new InstanceLifecycleResponse(command.CommandId, instanceName, false,
$"A deployment for instance {instanceName} is in progress; retry the enable after it completes.",
DateTimeOffset.UtcNow));
return;
}
var sender = replyTo;
// Row 101 (initiation half) — an is_enabled UPDATE is about to start off-thread.
@@ -1853,15 +1865,23 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// Lane A L3: is_enabled=1 has already committed; put it back so the durable row
// agrees with the runtime (and the sweep does not keep re-trying it). Row 101 bumps at
// both ends, as for every other mutation of deployed_configurations.
BumpStoreWriteGeneration("enable revert dispatch");
var revert = _storage.SetInstanceEnabledAsync(instanceName, false);
revert.ContinueWith(t =>
//
// Follow-up L3: ONLY for a name Akka will never accept. A uniqueness collision (a
// valid name still held by a terminating actor) is transient — reverting it would
// leave an instance the operator enabled (or a redeploy just started) durably
// disabled; left enabled, the sweep starts it once the name is free.
if (ShouldRevertEnableAfterCreateFailure(instanceName))
{
if (!t.IsCompletedSuccessfully)
_logger.LogError(t.Exception?.GetBaseException(),
"Failed to revert is_enabled for {Instance} after a failed enable", instanceName);
});
revert.ContinueWith(_ => new StoreWriteCompleted("enable revert")).PipeTo(Self);
BumpStoreWriteGeneration("enable revert dispatch");
var revert = _storage.SetInstanceEnabledAsync(instanceName, false);
revert.ContinueWith(t =>
{
if (!t.IsCompletedSuccessfully)
_logger.LogError(t.Exception?.GetBaseException(),
"Failed to revert is_enabled for {Instance} after a failed enable", instanceName);
});
revert.ContinueWith(_ => new StoreWriteCompleted("enable revert")).PipeTo(Self);
}
result.OriginalSender.Tell(new InstanceLifecycleResponse(
result.Command.CommandId, instanceName, false, createError, DateTimeOffset.UtcNow));
return;
@@ -1877,6 +1897,17 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
_logger.LogInformation("Instance {Instance} enabled", instanceName);
}
/// <summary>
/// Whether a failed enable should put <c>is_enabled</c> back to 0: only when the instance
/// name is one Akka refuses outright (it can never start, so leaving it enabled would only
/// make the sweep retry it forever). A collision on a VALID name is transient (review
/// 2026-10-21 follow-up L3).
/// </summary>
/// <param name="instanceName">The instance whose enable failed to create its actor.</param>
/// <returns><see langword="true"/> when the enable should be reverted.</returns>
internal static bool ShouldRevertEnableAfterCreateFailure(string instanceName) =>
!ActorPath.IsValidPathElement(instanceName);
/// <summary>
/// Reconcile self-heal (deferred register row 67): makes sure an instance whose config was
/// (re)stored out-of-band is actually RUNNING.