fix: prevent launcher exit disposal deadlock

This commit is contained in:
Erik 2026-08-18 08:43:29 +02:00
parent 15539a22a6
commit 0a934cf578
9 changed files with 2596 additions and 15 deletions

View file

@ -199,6 +199,13 @@ public sealed class LauncherProcessSupervisor : ILauncherProcessSupervisor
return;
}
StopProcess(process, timeout);
}
private static void StopProcess(
ILauncherChildProcess process,
TimeSpan timeout)
{
process.TryRequestGracefulStop();
process.CloseMainWindow();
if (!process.WaitForExit(timeout) && !process.HasExited)
@ -214,12 +221,22 @@ public sealed class LauncherProcessSupervisor : ILauncherProcessSupervisor
private void OnProcessExited(object? sender, EventArgs e)
{
int? exitCode;
lock (_gate)
int? exitCode = null;
if (sender is ILauncherChildProcess process)
{
exitCode = _process is { HasExited: true } process
? process.ExitCode
: null;
try
{
exitCode = process.HasExited ? process.ExitCode : null;
}
catch (Exception error)
when (error is InvalidOperationException
or ObjectDisposedException)
{
// Disposal can detach the child after its native exit
// callback has already captured this handler. The terminal
// transition is still authoritative; only the optional exit
// code became unavailable during teardown.
}
}
SetState(LauncherSessionState.Exited, exitCode);
@ -325,21 +342,26 @@ public sealed class LauncherProcessSupervisor : ILauncherProcessSupervisor
_disposed = true;
process = _process;
_process = null;
}
if (process is null)
{
return;
}
if (process is { HasExited: false })
{
Stop(DisposeStopTimeout);
StopProcess(process, DisposeStopTimeout);
}
lock (_gate)
{
if (_process is not null)
{
_process.Exited -= OnProcessExited;
_process.Dispose();
_process = null;
}
}
// Event removal and child disposal can wait for an already-running
// native Process.Exited callback. They must stay outside _gate: that
// callback commits the terminal state through SetState, which needs
// the same gate. Holding it here creates the exact
// supervisor-gate/Process-internals lock inversion this ownership
// transfer is intended to prevent.
process.Exited -= OnProcessExited;
process.Dispose();
}
}