79 lines
2.7 KiB
C#
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;
|
|
}
|