Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ public SafeProcessHandle(System.IntPtr existingHandle, bool ownsHandle) : base (
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.Runtime.Versioning.SupportedOSPlatformAttribute("maccatalyst")]
public void Kill() { }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.Runtime.Versioning.SupportedOSPlatformAttribute("maccatalyst")]
public bool Signal(System.Runtime.InteropServices.PosixSignal signal) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")]
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
[System.Runtime.Versioning.SupportedOSPlatformAttribute("maccatalyst")]
public static Microsoft.Win32.SafeHandles.SafeProcessHandle Start(System.Diagnostics.ProcessStartInfo startInfo) { throw null; }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,31 @@ protected override bool ReleaseHandle()
// On Unix, we don't use process descriptors yet, so we can't get PID.
private static int GetProcessIdCore() => throw new PlatformNotSupportedException();

private bool SignalCore(PosixSignal signal)
{
int signalNumber = Interop.Sys.GetPlatformSignalNumber(signal);
if (signalNumber == 0)
{
throw new PlatformNotSupportedException();
Comment thread
adamsitnik marked this conversation as resolved.
}

int killResult = Interop.Sys.Kill(ProcessId, (Interop.Sys.Signals)signalNumber);
if (killResult != 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();

// Return false if the process has already exited (or never existed).
if (errorInfo.Error == Interop.Error.ESRCH)
{
return false;
}

throw new Win32Exception(errorInfo.RawErrno); // same exception as on Windows
}

return true;
}

private static SafeProcessHandle StartCore(ProcessStartInfo startInfo, SafeFileHandle? stdinHandle, SafeFileHandle? stdoutHandle, SafeFileHandle? stderrHandle)
{
SafeProcessHandle startedProcess = StartCore(startInfo, stdinHandle, stdoutHandle, stderrHandle, out ProcessWaitState.Holder? waitStateHolder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
Expand Down Expand Up @@ -282,5 +283,31 @@ ref processInfo // pointer to PROCESS_INFORMATION
}

private int GetProcessIdCore() => Interop.Kernel32.GetProcessId(this);

private bool SignalCore(PosixSignal signal)
{
// On Windows, only SIGKILL is supported, mapped to TerminateProcess.
if (signal != PosixSignal.SIGKILL)
{
throw new PlatformNotSupportedException();
Comment thread
adamsitnik marked this conversation as resolved.
}

if (!Interop.Kernel32.TerminateProcess(this, -1))
Comment thread
adamsitnik marked this conversation as resolved.
{
Comment thread
adamsitnik marked this conversation as resolved.
int errorCode = Marshal.GetLastWin32Error();

// Return false if the process has already exited.
if (errorCode == Interop.Errors.ERROR_ACCESS_DENIED &&
Interop.Kernel32.GetExitCodeProcess(this, out int exitCode) &&
exitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE)
{
return false;
}

throw new Win32Exception(errorCode);
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Runtime.InteropServices;

namespace Microsoft.Win32.SafeHandles
{
Expand Down Expand Up @@ -120,6 +121,47 @@ public static SafeProcessHandle Start(ProcessStartInfo startInfo)
return StartCore(startInfo, childInputHandle, childOutputHandle, childErrorHandle);
}

/// <summary>
/// Sends a request to the OS to terminate the process.
/// </summary>
/// <remarks>
/// This method does not throw if the process has already exited.
/// On Unix, this sends <c>SIGKILL</c> to the process.
/// </remarks>
/// <exception cref="InvalidOperationException">The handle is invalid.</exception>
/// <exception cref="System.ComponentModel.Win32Exception">The process could not be terminated.</exception>
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
Comment thread
adamsitnik marked this conversation as resolved.
public void Kill()
{
Validate();
SignalCore(PosixSignal.SIGKILL);
Comment thread
jkotas marked this conversation as resolved.
}

/// <summary>
/// Sends a signal to the process.
/// </summary>
/// <param name="signal">The signal to send.</param>
/// <returns>
/// <see langword="true"/> if the signal was sent successfully;
/// <see langword="false"/> if the process has already exited (or never existed) and the signal was not delivered.
/// </returns>
/// <remarks>
/// On Windows, only <see cref="PosixSignal.SIGKILL"/> is supported and is mapped to <see cref="Kill"/>.
/// </remarks>
/// <exception cref="InvalidOperationException">The handle is invalid.</exception>
/// <exception cref="PlatformNotSupportedException">The specified signal is not supported on this platform.</exception>
/// <exception cref="System.ComponentModel.Win32Exception">The signal could not be sent.</exception>
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
public bool Signal(PosixSignal signal)
{
Validate();
return SignalCore(signal);
}

private void Validate()
{
if (IsInvalid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@
Link="Common\Interop\Unix\Interop.InitializeTerminalAndSignalHandling.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.Kill.cs"
Link="Common\Interop\Unix\Interop.Kill.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.PosixSignal.cs"
Link="Common\Interop\Unix\System.Native\Interop.PosixSignal.cs" />
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.ReadLink.cs"
Link="Common\Interop\Unix\Interop.ReadLink.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.RegisterForSigChld.cs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Xunit;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.DotNet.XUnitExtensions;
using Microsoft.Win32.SafeHandles;
Comment thread
adamsitnik marked this conversation as resolved.
Outdated

namespace System.Diagnostics.Tests
{
Expand Down Expand Up @@ -1114,8 +1115,8 @@ public void ChildProcess_WithParentSignalHandler_CanReceiveSignals()

AssertRemoteProcessStandardOutputLine(childHandle, ChildReadyMessage, WaitInMS);

// Send SIGCONT to the child process
SendSignal(PosixSignal.SIGCONT, childHandle.Process.Id);
Comment thread
adamsitnik marked this conversation as resolved.
// Send SIGCONT to the child process using SafeProcessHandle.Signal
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
Assert.True(childHandle.Process.SafeHandle.Signal(PosixSignal.SIGCONT));

Assert.True(childHandle.Process.WaitForExit(WaitInMS));
Assert.Equal(RemotelyInvokable.SuccessExitCode, childHandle.Process.ExitCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Win32.SafeHandles;
using Xunit;
Expand Down Expand Up @@ -94,5 +96,123 @@ public void Start_WithRedirectedStreams_ThrowsInvalidOperationException(

Assert.Throws<InvalidOperationException>(() => SafeProcessHandle.Start(startInfo));
}

[Fact]
public void Kill_InvalidHandle_ThrowsInvalidOperationException()
{
using SafeProcessHandle invalidHandle = new SafeProcessHandle();
Assert.Throws<InvalidOperationException>(() => invalidHandle.Kill());
}

[Fact]
public void Signal_InvalidHandle_ThrowsInvalidOperationException()
{
using SafeProcessHandle invalidHandle = new SafeProcessHandle();
Assert.Throws<InvalidOperationException>(() => invalidHandle.Signal(PosixSignal.SIGKILL));
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void Kill_RunningProcess_Terminates()
{
Process process = CreateProcess(static () =>
{
Thread.Sleep(Timeout.Infinite);
return RemoteExecutor.SuccessExitCode;
});

using SafeProcessHandle processHandle = SafeProcessHandle.Start(process.StartInfo);
using Process fetchedProcess = Process.GetProcessById(processHandle.ProcessId);

processHandle.Kill();

Assert.True(fetchedProcess.WaitForExit(WaitInMS));
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void Kill_AlreadyExited_DoesNotThrow()
{
Process process = CreateProcess(static () => RemoteExecutor.SuccessExitCode);
process.Start();
SafeProcessHandle handle = process.SafeHandle;

Assert.True(process.WaitForExit(WaitInMS));

// Kill after the process has exited should not throw.
handle.Kill();
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void Signal_SIGKILL_RunningProcess_ReturnsTrue()
{
Process process = CreateProcess(static () =>
{
Thread.Sleep(Timeout.Infinite);
return RemoteExecutor.SuccessExitCode;
});

using SafeProcessHandle processHandle = SafeProcessHandle.Start(process.StartInfo);
using Process fetchedProcess = Process.GetProcessById(processHandle.ProcessId);

bool delivered = processHandle.Signal(PosixSignal.SIGKILL);

Assert.True(delivered);
Assert.True(fetchedProcess.WaitForExit(WaitInMS));
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void Signal_SIGKILL_AlreadyExited_ReturnsFalse()
{
Process process = CreateProcess(static () => RemoteExecutor.SuccessExitCode);
process.Start();
SafeProcessHandle handle = process.SafeHandle;

Assert.True(process.WaitForExit(WaitInMS));

// Signal after the process has exited should return false.
Assert.False(handle.Signal(PosixSignal.SIGKILL));
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[PlatformSpecific(TestPlatforms.Windows)]
public void Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedException()
{
Process process = CreateProcess(static () =>
{
Thread.Sleep(Timeout.Infinite);
return RemoteExecutor.SuccessExitCode;
});

using SafeProcessHandle processHandle = SafeProcessHandle.Start(process.StartInfo);
using Process fetchedProcess = Process.GetProcessById(processHandle.ProcessId);

try
{
Assert.Throws<PlatformNotSupportedException>(() => processHandle.Signal(PosixSignal.SIGTERM));
}
finally
{
processHandle.Kill();
fetchedProcess.WaitForExit(WaitInMS);
}
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[SkipOnPlatform(TestPlatforms.Windows, "SIGTERM is not supported on Windows.")]
public void Signal_SIGTERM_RunningProcess_ReturnsTrue()
{
Process process = CreateProcess(static () =>
{
Thread.Sleep(Timeout.Infinite);
return RemoteExecutor.SuccessExitCode;
});

using SafeProcessHandle processHandle = SafeProcessHandle.Start(process.StartInfo);
using Process fetchedProcess = Process.GetProcessById(processHandle.ProcessId);

bool delivered = processHandle.Signal(PosixSignal.SIGTERM);

Assert.True(delivered);
Assert.True(fetchedProcess.WaitForExit(WaitInMS));
}
Comment thread
adamsitnik marked this conversation as resolved.
}
}
Loading