acdream/src/AcDream.App/Net/LiveSessionResetPlan.cs

79 lines
2.7 KiB
C#

namespace AcDream.App.Net;
/// <summary>One named owner operation in the live-session reset transaction.</summary>
internal sealed record LiveSessionResetStage(string Name, Action Reset);
/// <summary>
/// Executes every session-state reset stage even when an earlier owner fails.
/// A failed attempt throws after the complete manifest has run; lifecycle code
/// therefore cannot construct the next session until a later attempt converges.
/// </summary>
internal sealed class LiveSessionResetPlan
{
private readonly LiveSessionResetStage[] _stages;
private int _executing;
public LiveSessionResetPlan(IEnumerable<LiveSessionResetStage> stages)
{
ArgumentNullException.ThrowIfNull(stages);
_stages = stages.ToArray();
var names = new HashSet<string>(StringComparer.Ordinal);
foreach (LiveSessionResetStage stage in _stages)
{
ArgumentNullException.ThrowIfNull(stage);
if (string.IsNullOrWhiteSpace(stage.Name))
throw new ArgumentException("Reset stage names must be non-empty.", nameof(stages));
ArgumentNullException.ThrowIfNull(stage.Reset);
if (!names.Add(stage.Name))
throw new ArgumentException(
$"Duplicate live-session reset stage '{stage.Name}'.",
nameof(stages));
}
}
public IReadOnlyList<string> StageNames =>
Array.ConvertAll(_stages, static stage => stage.Name);
public void Execute()
{
if (Interlocked.Exchange(ref _executing, 1) != 0)
throw new InvalidOperationException(
"Live-session reset cannot run concurrently or reentrantly.");
List<Exception>? failures = null;
try
{
foreach (LiveSessionResetStage stage in _stages)
{
try
{
stage.Reset();
}
catch (Exception error)
{
(failures ??= []).Add(new LiveSessionResetStageException(
stage.Name,
error));
}
}
}
finally
{
Volatile.Write(ref _executing, 0);
}
if (failures is not null)
throw new AggregateException(
"Live-session state did not converge; a new session must not start.",
failures);
}
}
internal sealed class LiveSessionResetStageException(
string stageName,
Exception innerException) : Exception(
$"Live-session reset stage '{stageName}' failed.",
innerException)
{
public string StageName { get; } = stageName;
}