Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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,46 @@ 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 void KillCore()
{
int killResult = Interop.Sys.Kill(ProcessId, Interop.Sys.Signals.SIGKILL);
if (killResult != 0)
{
Interop.Error error = Interop.Sys.GetLastError();

// Don't throw if the process has already exited.
if (error != Interop.Error.ESRCH)
{
throw new Win32Exception(); // same exception as on Windows
}
}
}
Comment thread
adamsitnik marked this conversation as resolved.
Outdated

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.Error error = Interop.Sys.GetLastError();

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

throw new Win32Exception(); // same exception as on Windows
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
}

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,49 @@ ref processInfo // pointer to PROCESS_INFORMATION
}

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

private void KillCore()
{
if (!Interop.Kernel32.TerminateProcess(this, -1))
{
Win32Exception exception = new Win32Exception();

// Don't throw if the process has already exited.
if (exception.NativeErrorCode == Interop.Errors.ERROR_ACCESS_DENIED &&
Interop.Kernel32.GetExitCodeProcess(this, out int exitCode) &&
exitCode != Interop.Kernel32.HandleOptions.STILL_ACTIVE)
{
return;
}

throw exception;
}
}
Comment thread
adamsitnik marked this conversation as resolved.
Outdated

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.
Win32Exception exception = new Win32Exception();
Comment thread
adamsitnik marked this conversation as resolved.
Outdated

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

throw exception;
}

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>
/// Terminates the process immediately.
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
/// </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();
KillCore();
}

/// <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 and the signal was not delivered.
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
/// </returns>
/// <remarks>
/// On Windows, only <see cref="PosixSignal.SIGKILL"/> is supported and is mapped to <c>TerminateProcess</c>.
Comment thread
adamsitnik marked this conversation as resolved.
Outdated
/// </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 @@ -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