Skip to content
22 changes: 19 additions & 3 deletions src/Dotnet.Watch/Watch/Process/ProcessRunner.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -328,7 +330,7 @@ private static void TerminateProcess(Process process, ProcessState state, ILogge
}
else
{
TerminateUnixProcess(state, logger, force);
TerminateUnixProcess(process, state, logger, force);
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 20 additions & 1 deletion src/Dotnet.Watch/Watch/UI/PhysicalConsole.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -55,7 +58,23 @@ private async Task ListenToStandardInputAsync()
else
{
Console.WriteLine($"Sending SIGTERM to {processId}");
error = ProcessUtilities.SendPosixSignal(processId, ProcessUtilities.SIGTERM);
Comment thread
adamsitnik marked this conversation as resolved.
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)
Expand Down
11 changes: 0 additions & 11 deletions src/Dotnet.Watch/Watch/Utilities/ProcessUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down
Loading