fix: prevent launcher exit disposal deadlock
This commit is contained in:
parent
15539a22a6
commit
0a934cf578
9 changed files with 2596 additions and 15 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue