feat(launcher): implement verified atomic updates

This commit is contained in:
Erik 2026-08-14 22:09:34 +02:00
parent 2198a0cc8e
commit 2d2a5b5046
34 changed files with 6755 additions and 61 deletions

View file

@ -1,6 +1,7 @@
using AcDream.Launcher.Core.Launching;
using AcDream.Launcher.Core.Profiles;
using AcDream.Launcher.Core.Status;
using AcDream.Launcher.Core.Updates;
using AcDream.Platform;
namespace AcDream.Launcher.Core.Orchestration;
@ -26,6 +27,7 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
private readonly ILauncherProcessSupervisorFactory _supervisorFactory;
private readonly IStatusEventSourceFactory _statusSourceFactory;
private readonly Func<string> _sessionIdFactory;
private readonly UpdateSessionBarrier _updateSessionBarrier;
private readonly List<ManagedActivity> _activities = [];
private LauncherInstallRecord? _installRecord;
@ -42,7 +44,8 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
ILauncherProcessSupervisorFactory? supervisorFactory = null,
IStatusEventSourceFactory? statusSourceFactory = null,
Func<string>? sessionIdFactory = null,
string? installationStatus = null)
string? installationStatus = null,
UpdateSessionBarrier? updateSessionBarrier = null)
{
_profileStore = profileStore ?? throw new ArgumentNullException(nameof(profileStore));
_paths = paths ?? throw new ArgumentNullException(nameof(paths));
@ -53,6 +56,8 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
_supervisorFactory = supervisorFactory ?? new LauncherProcessSupervisorFactory();
_statusSourceFactory = statusSourceFactory ?? new StatusFileTailerFactory();
_sessionIdFactory = sessionIdFactory ?? CreateSessionId;
_updateSessionBarrier = updateSessionBarrier
?? new UpdateSessionBarrier(paths.DataDirectory);
_installationStatus = installationStatus
?? (installRecord is null
? FirstRunRequired
@ -629,10 +634,18 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
{
ILauncherProcessSupervisor? supervisor = null;
string? password = request.Password;
bool hostStarted = false;
try
{
request.Cancellation.Token.ThrowIfCancellationRequested();
UpdateSessionBarrier.SessionLease sessionLease =
_updateSessionBarrier.AcquireSession();
lock (_gate)
{
request.Activity.UpdateSessionLease = sessionLease;
}
ComposedSessionConfig composed = request.IsProbe
? _configService.ComposeProbeAndWrite(
request.Server,
@ -674,6 +687,7 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
request.Activity.LaunchMode!.Value,
composed.ConfigFilePath);
supervisor.Start(processSpec, password);
hostStarted = true;
request.Password = null;
password = null;
@ -728,6 +742,10 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
finally
{
request.Password = null;
if (!hostStarted)
{
ReleaseUpdateSessionLease(request.Activity);
}
}
}
@ -735,6 +753,7 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
ManagedActivity activity,
LauncherSessionState processState)
{
UpdateSessionBarrier.SessionLease? sessionLease = null;
try
{
lock (_gate)
@ -774,10 +793,13 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
{
activity.Status = activity.HostTerminalStatus;
}
sessionLease = activity.UpdateSessionLease;
activity.UpdateSessionLease = null;
break;
}
}
sessionLease?.Dispose();
RaiseStateChanged();
}
catch
@ -1192,6 +1214,15 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
activity.Supervisor.Dispose();
activity.Supervisor = null;
}
ReleaseUpdateSessionLease(activity);
}
private static void ReleaseUpdateSessionLease(ManagedActivity activity)
{
UpdateSessionBarrier.SessionLease? lease =
Interlocked.Exchange(ref activity.UpdateSessionLease, null);
lease?.Dispose();
}
private void ThrowIfDisposed()
@ -1252,6 +1283,8 @@ public sealed class LauncherOrchestrator : ILauncherOrchestrator
public CancellationTokenSource? StartCancellation { get; set; }
public UpdateSessionBarrier.SessionLease? UpdateSessionLease;
public object StatusReadGate { get; } = new();
public bool IsActive => State is not (