diff --git a/src/Dotnet.Watch/Watch/Process/ProcessRunner.cs b/src/Dotnet.Watch/Watch/Process/ProcessRunner.cs index 3ce6761e7030..1b0e2ac2d8bc 100644 --- a/src/Dotnet.Watch/Watch/Process/ProcessRunner.cs +++ b/src/Dotnet.Watch/Watch/Process/ProcessRunner.cs @@ -1,7 +1,9 @@ // 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.Versioning; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch; @@ -328,7 +330,7 @@ private static void TerminateProcess(Process process, ProcessState state, ILogge } else { - TerminateUnixProcess(state, logger, force); + TerminateUnixProcess(process, state, logger, force); } } } @@ -364,12 +366,26 @@ private static void TerminateWindowsProcess(Process process, ProcessState state, } } - private static void TerminateUnixProcess(ProcessState state, ILogger logger, bool force) + [UnsupportedOSPlatform("windows")] + private static void TerminateUnixProcess(Process process, ProcessState state, ILogger logger, bool force) { + var signal = force ? PosixSignal.SIGKILL : PosixSignal.SIGTERM; var signalName = force ? "SIGKILL" : "SIGTERM"; logger.Log(MessageDescriptor.TerminatingProcess, state.ProcessId, signalName); - var error = ProcessUtilities.SendPosixSignal(state.ProcessId, signal: force ? ProcessUtilities.SIGKILL : ProcessUtilities.SIGTERM); + string? error = null; + try + { + process.SafeHandle.Signal(signal); + } + catch (Win32Exception ex) + { + // A process that has already exited is handled by Signal's non-exception return path. + // This catch is for exceptional failures, such as attempting to signal a process + // that we don't have permission to kill. + error = ex.Message; + } + if (error != null) { logger.Log(MessageDescriptor.FailedToSendSignalToProcess, signalName, state.ProcessId, error); diff --git a/src/Dotnet.Watch/Watch/UI/PhysicalConsole.cs b/src/Dotnet.Watch/Watch/UI/PhysicalConsole.cs index 0e6004612093..f1563660131f 100644 --- a/src/Dotnet.Watch/Watch/UI/PhysicalConsole.cs +++ b/src/Dotnet.Watch/Watch/UI/PhysicalConsole.cs @@ -1,6 +1,9 @@ // 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; + namespace Microsoft.DotNet.Watch; internal sealed class PhysicalConsole : IConsole @@ -55,7 +58,23 @@ private async Task ListenToStandardInputAsync() else { Console.WriteLine($"Sending SIGTERM to {processId}"); - error = ProcessUtilities.SendPosixSignal(processId, ProcessUtilities.SIGTERM); + error = null; + try + { + using var process = Process.GetProcessById(processId); + process.SafeHandle.Signal(PosixSignal.SIGTERM); + } + catch (Win32Exception ex) + { + // Signal returns false when the given process has already exited. + // So it can throw only when we try to kill a non-child process + // that we don't have permission to kill. + error = ex.Message; + } + catch (ArgumentException) + { + // Process has already exited + } } if (error != null) diff --git a/src/Dotnet.Watch/Watch/Utilities/ProcessUtilities.cs b/src/Dotnet.Watch/Watch/Utilities/ProcessUtilities.cs index a215e7be62ab..42e47eda86a6 100644 --- a/src/Dotnet.Watch/Watch/Utilities/ProcessUtilities.cs +++ b/src/Dotnet.Watch/Watch/Utilities/ProcessUtilities.cs @@ -5,9 +5,6 @@ namespace Microsoft.DotNet.Watch; internal static class ProcessUtilities { - public const int SIGKILL = 9; - public const int SIGTERM = 15; - public static string? SendWindowsCtrlCEvent(int processId) { const uint CTRL_C_EVENT = 0; @@ -28,14 +25,6 @@ internal static class ProcessUtilities static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent, uint dwProcessGroupId); } - public static string? SendPosixSignal(int processId, int signal) - { - return sys_kill(processId, signal) == 0 ? null : GetLastPInvokeErrorMessage(); - - [DllImport("libc", SetLastError = true, EntryPoint = "kill")] - static extern int sys_kill(int pid, int sig); - } - private static string GetLastPInvokeErrorMessage() { var error = Marshal.GetLastPInvokeError();