diff --git a/src/Shared/ProcessSignaler.cs b/src/Shared/ProcessSignaler.cs index 0e3e7cb7ce6..bda6a79ad2c 100644 --- a/src/Shared/ProcessSignaler.cs +++ b/src/Shared/ProcessSignaler.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; @@ -49,20 +50,31 @@ public static void ForceKill(int pid, DateTimeOffset? expectedStartTime, ILogger public static Process? TryGetRunningProcess(int pid, DateTimeOffset? expectedStartTime, ILogger logger) { + Process? process = null; try { - var process = Process.GetProcessById(pid); - if (expectedStartTime is not null && !AreClose(expectedStartTime, process.StartTime)) + process = Process.GetProcessById(pid); + if (process.HasExited) { - logger.LogDebug("Process {Pid} start time {ProcessStartTime} does not match expected start time {ExpectedStartTime}", pid, process.StartTime, expectedStartTime); process.Dispose(); - return null; // Do not return processes that do not match the expected start time + return null; } - if (process.HasExited) + if (expectedStartTime is not null) { - process.Dispose(); - return null; + var processStartTime = process.StartTime; + if (!AreClose(expectedStartTime, processStartTime)) + { + logger.LogDebug("Process {Pid} start time {ProcessStartTime} does not match expected start time {ExpectedStartTime}", pid, processStartTime, expectedStartTime); + process.Dispose(); + return null; // Do not return processes that do not match the expected start time + } + + if (process.HasExited) + { + process.Dispose(); + return null; + } } return process; @@ -70,11 +82,23 @@ public static void ForceKill(int pid, DateTimeOffset? expectedStartTime, ILogger catch (ArgumentException) { // Process doesn't exist - already terminated. + process?.Dispose(); return null; } catch (InvalidOperationException) { // Process has already exited. + process?.Dispose(); + return null; + } + catch (Win32Exception ex) + { + // Process inspection can race with process exit. On macOS, StartTime can throw: + // Win32Exception (3): Unable to retrieve the specified information about the process or thread. It may have exited or may be privileged. + // If we cannot inspect the process enough to prove it is the expected target, do + // not signal or kill it. + logger.LogDebug(ex, "Could not inspect process {Pid}. Treating it as not running.", pid); + process?.Dispose(); return null; } }