fix(launcher): Campaign LA LA3 narrow review fixes
This commit is contained in:
parent
26feba8186
commit
347a1a5d16
9 changed files with 495 additions and 145 deletions
|
|
@ -1,3 +1,5 @@
|
|||
using System.Runtime.ExceptionServices;
|
||||
|
||||
namespace AcDream.Launcher.Core.Launching;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -10,19 +12,41 @@ public sealed class LauncherProcessSupervisor : IDisposable
|
|||
{
|
||||
private readonly ILauncherChildProcessFactory _factory;
|
||||
private readonly object _gate = new();
|
||||
private readonly Queue<LauncherSessionState> _pendingStateChanges = [];
|
||||
private ILauncherChildProcess? _process;
|
||||
private LauncherSessionState _state = LauncherSessionState.Starting;
|
||||
private int? _exitCode;
|
||||
private bool _publishingStateChanges;
|
||||
|
||||
public LauncherProcessSupervisor(ILauncherChildProcessFactory? factory = null)
|
||||
{
|
||||
_factory = factory ?? new SystemChildProcessFactory();
|
||||
}
|
||||
|
||||
public LauncherSessionState State { get; private set; } = LauncherSessionState.Starting;
|
||||
public LauncherSessionState State
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Set once <see cref="State"/> reaches
|
||||
/// <see cref="LauncherSessionState.Exited"/>; null before then.
|
||||
/// </summary>
|
||||
public int? ExitCode { get; private set; }
|
||||
public int? ExitCode
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
return _exitCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Fires on every <see cref="LauncherSessionState"/>
|
||||
/// transition, in order.</summary>
|
||||
|
|
@ -148,14 +172,15 @@ public sealed class LauncherProcessSupervisor : IDisposable
|
|||
|
||||
private void OnProcessExited(object? sender, EventArgs e)
|
||||
{
|
||||
ILauncherChildProcess? process;
|
||||
int? exitCode;
|
||||
lock (_gate)
|
||||
{
|
||||
process = _process;
|
||||
exitCode = _process is { HasExited: true } process
|
||||
? process.ExitCode
|
||||
: null;
|
||||
}
|
||||
|
||||
ExitCode = process is { HasExited: true } ? process.ExitCode : null;
|
||||
SetState(LauncherSessionState.Exited);
|
||||
SetState(LauncherSessionState.Exited, exitCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -171,19 +196,79 @@ public sealed class LauncherProcessSupervisor : IDisposable
|
|||
/// — without this guard, "Running" would silently resurrect a
|
||||
/// process that has already reported its exit.
|
||||
/// </summary>
|
||||
private void SetState(LauncherSessionState state)
|
||||
private void SetState(LauncherSessionState state, int? exitCode = null)
|
||||
{
|
||||
bool publish;
|
||||
lock (_gate)
|
||||
{
|
||||
if (State == LauncherSessionState.Exited)
|
||||
if (_state == LauncherSessionState.Exited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
State = state;
|
||||
_state = state;
|
||||
if (state == LauncherSessionState.Exited)
|
||||
{
|
||||
_exitCode = exitCode;
|
||||
}
|
||||
|
||||
_pendingStateChanges.Enqueue(state);
|
||||
publish = !_publishingStateChanges;
|
||||
if (publish)
|
||||
{
|
||||
_publishingStateChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(this, state);
|
||||
if (publish)
|
||||
{
|
||||
PublishPendingStateChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drains state notifications through one publisher. Transition storage
|
||||
/// stays under <see cref="_gate"/>, but user callbacks run outside it so
|
||||
/// they may re-enter the supervisor or wait for another thread reading
|
||||
/// state without deadlocking. A concurrent/re-entrant transition queues
|
||||
/// behind the notification already in flight, preserving storage order in
|
||||
/// the externally observed event stream.
|
||||
/// </summary>
|
||||
private void PublishPendingStateChanges()
|
||||
{
|
||||
Exception? firstException = null;
|
||||
while (true)
|
||||
{
|
||||
LauncherSessionState state;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_pendingStateChanges.Count == 0)
|
||||
{
|
||||
_publishingStateChanges = false;
|
||||
break;
|
||||
}
|
||||
|
||||
state = _pendingStateChanges.Dequeue();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
StateChanged?.Invoke(this, state);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Preserve the previous propagation behavior, but finish
|
||||
// publishing any transition already committed concurrently
|
||||
// (especially terminal Exited) before rethrowing the first
|
||||
// observer failure to the initiating caller.
|
||||
firstException ??= ex;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstException is not null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(firstException).Throw();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue