Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Implement SafeProcessHandle.Kill and Signal by Copilot · Pull Request #126313 · dotnet/runtime · GitHub
Skip to content

Implement SafeProcessHandle.Kill and Signal - #126313

Merged
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal
Apr 1, 2026
Merged

Implement SafeProcessHandle.Kill and Signal#126313
adamsitnik merged 14 commits into
mainfrom
copilot/implement-safeprocesshandle-kill-signal

Conversation

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Adds Kill() and Signal(PosixSignal) to SafeProcessHandle, enabling callers to terminate or signal a process via a handle without going through Process.

Description

Public API (SafeProcessHandle.cs + ref assembly)

  • Kill() — sends a request to the OS to terminate the process; silently no-ops if already exited (matches Process.Kill semantics). On Windows, the handle must have PROCESS_TERMINATE access.
  • Signal(PosixSignal) — sends an arbitrary signal; returns false if process no longer exists (or never existed), true on delivery, throws PlatformNotSupportedException for unsupported signals. On Windows, the handle must have PROCESS_TERMINATE access.

Both methods validate the handle (throw InvalidOperationException on invalid handle), carry the same [UnsupportedOSPlatform("ios/tvos")] / [SupportedOSPlatform("maccatalyst")] annotations as Start, and throw PlatformNotSupportedException at runtime on iOS/tvOS (matching the ProcessUtils.PlatformDoesNotSupportProcessStartAndKill pattern used by Process.Kill).

Windows (SafeProcessHandle.Windows.cs)

  • SignalCore: only SIGKILL is supported (maps to TerminateProcess), matching PosixSignalRegistration.Create exception behavior for unsupported signals. Retrieves the error code via Marshal.GetLastWin32Error() before any checks and only constructs Win32Exception(errorCode) when actually throwing. Detects already-exited processes via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Unix (SafeProcessHandle.Unix.cs)

  • SignalCore: checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first (throws PlatformNotSupportedException on iOS/tvOS); uses Interop.Sys.GetPlatformSignalNumber(signal) to convert the managed PosixSignal to its platform-native signal number (throws PlatformNotSupportedException if unsupported, matching PosixSignalRegistration.Create behavior); passes the native signal number directly to Interop.Sys.Kill; returns false on ESRCH; uses Interop.Sys.GetLastErrorInfo() and passes errorInfo.RawErrno to the Win32Exception constructor.
  • No separate KillCore; Kill() calls SignalCore(PosixSignal.SIGKILL) directly from the shared file.

Native (pal_process.c / pal_signal.c)

  • SystemNative_Kill simplified to call kill(pid, signal) directly with no PAL mapping switch — it now takes the platform-native signal number, consistent with other System.Native APIs such as SystemNative_EnablePosixSignalHandling. The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) has been removed from pal_process.h.
  • SystemNative_GetPlatformSIGSTOP() moved to pal_signal.h/pal_signal.c (right below SystemNative_GetPlatformSignalNumber) since it is signal-related. Dummy implementations returning 0 are provided in pal_signal_wasm.c for both SystemNative_GetPlatformSIGSTOP and SystemNative_GetPlatformSignalNumber for BROWSER and WASI platforms.
  • entrypoints.c: moved the DllImportEntry(SystemNative_GetPlatformSIGSTOP) registration from the process group (between SystemNative_Kill and SystemNative_GetPid) to the signal group (alongside SystemNative_GetPlatformSignalNumber, SystemNative_EnablePosixSignalHandling, etc.), consistent with the function's new home in pal_signal.c/pal_signal.h.

Process.Kill refactoring (Process.Windows.cs)

  • Process.Kill() on Windows now delegates to SafeProcessHandle.Kill() after obtaining the handle, eliminating the duplicated TerminateProcess + error-handling logic.

Interop (Interop.Kill.cs / Interop.PosixSignal.cs)

  • Removed the Signals managed enum; Interop.Sys.Kill now takes a plain int signal parameter (platform-native number).
  • Interop.Sys.GetPlatformSIGSTOP() P/Invoke moved to Interop.PosixSignal.cs alongside GetPlatformSignalNumber, keeping all signal-number interop co-located.
  • All existing callers updated: Process.Unix.cs uses GetPlatformSignalNumber(PosixSignal.SIGKILL) and GetPlatformSIGSTOP(); ProcessWaitState.Unix.cs and ProcessManager.Unix.cs pass 0 directly for the "probe" call.

Project / interop

  • Added Interop.PosixSignal.cs to the Unix ItemGroup in System.Diagnostics.Process.csproj to expose GetPlatformSignalNumber and GetPlatformSIGSTOP in the main assembly.

Tests

  • SafeProcessHandleTests.cs: Invalid handle → InvalidOperationException; Kill / Signal(SIGKILL) on running process terminates it; Kill on exited process does not throw; Signal(SIGKILL) returns false on exited process; Windows: non-SIGKILL signal → PlatformNotSupportedException; Unix: Signal(SIGTERM) on running process returns true and terminates it; Windows: Kill_HandleWithoutTerminatePermission_ThrowsWin32Exception — opens a handle with only PROCESS_QUERY_LIMITED_INFORMATION and verifies Kill() throws Win32Exception.
  • ProcessTests.Unix.cs: Updated ChildProcess_WithParentSignalHandler_CanReceiveSignals to use SafeProcessHandle.Signal instead of the raw SendSignal helper. Refactored SendSignal helper to accept a Process parameter — Unix calls process.SafeHandle.Signal(signal) directly (simplified to a single-line Assert.True). Removed the class-level SIGKILL constant and kill P/Invoke declaration; Kill_ExitedNonChildProcess_DoesNotThrow now uses SafeHandle.Signal(PosixSignal.SIGKILL) and Process.HasExited.

CopilotAI linked an issue Mar 30, 2026 that may be closed by this pull request
@github-actionsgithub-actionsBot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 30, 2026
@adamsitnikadamsitnik added area-System.Diagnostics.Process and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Mar 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f1233ff0-2352-488d-ae71-9947c67f09c6
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
CopilotAI changed the title [WIP] Add SafeProcessHandle.Kill and Signal methodsImplement SafeProcessHandle.Kill and SignalMar 30, 2026
CopilotAI requested a review from adamsitnikMarch 30, 2026 13:50

@adamsitnikadamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

…r handling
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/33138282-b24e-4a35-b495-385af456c851
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

CopilotAI commented Mar 30, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot address my feedback, also do search for other places in the repo where the new Signal API could be used

All feedback addressed in 7c03c8c. For other usages: the main candidate in the codebase is the SendSignal helper in tests (ProcessTests.cs:173, :246, :256, :260) which sends signals via kill() directly. However, those tests operate against a Process instance using its Id, while our Signal API requires a SafeProcessHandle — on Unix the handle's ProcessId is only set when the process was started via SafeProcessHandle.Start (or Process.Start). The ChildProcess_WithParentSignalHandler_CanReceiveSignals test was the natural fit since it already uses RemoteInvokeHandle with a started Process, so Process.SafeHandle.Signal() works correctly there.

CopilotAI requested a review from adamsitnikMarch 30, 2026 14:07
@adamsitnik
adamsitnik marked this pull request as ready for review March 30, 2026 14:14
CopilotAI review requested due to automatic review settings March 30, 2026 14:14
@adamsitnikadamsitnik added this to the 11.0.0 milestone Mar 30, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds process-termination and signal-sending capabilities directly to SafeProcessHandle, enabling callers to manage processes via handles without going through Process, with platform-specific implementations for Windows and Unix plus corresponding test updates.

Changes:

  • Added SafeProcessHandle.Kill() and SafeProcessHandle.Signal(PosixSignal) public APIs (with ref-assembly updates).
  • Implemented SignalCore on Windows (via TerminateProcess) and Unix (via kill(2) + GetPlatformSignalNumber).
  • Added/updated tests to validate new APIs and switched an existing Unix test to use SafeProcessHandle.Signal.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
FileDescription
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.csIntroduces the new public Kill/Signal APIs and validation path.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.csImplements Windows SignalCore mapping SIGKILL to TerminateProcess with exited-process detection.
src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Unix.csImplements Unix SignalCore using GetPlatformSignalNumber + kill, returning false on ESRCH.
src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csprojAdds Interop.PosixSignal.cs to the Unix compile item group.
src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.csUpdates the public ref surface for SafeProcessHandle with Kill/Signal.
src/libraries/System.Diagnostics.Process/tests/SafeProcessHandleTests.csAdds unit tests covering invalid-handle, running-process, exited-process, and platform-specific behaviors.
src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.csUpdates signal-delivery test to use SafeProcessHandle.Signal instead of a raw P/Invoke helper.

Comment threadsrc/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Copilot Code Review — PR #126313

Note

This review was generated by GitHub Copilot using Claude Opus 4.6.

Holistic Assessment

Motivation: Justified. This is the next step in the approved API expansion (#125838, api-approved by @bartonjs on 2026-03-24). Following the merged Start/ProcessId PR (#126192), Kill and Signal are the natural additions to enable callers to manage process lifecycle via SafeProcessHandle without requiring a Process object. The motivation is clear and real.

Approach: Sound. The implementation follows the approved API shape exactly, builds on the existing SafeProcessHandle partial class pattern, and properly separates platform-specific behavior. The native code simplification (removing the Signals PAL enum and having SystemNative_Kill accept platform-native signal numbers directly) is a good cleanup that aligns with how other System.Native APIs like SystemNative_EnablePosixSignalHandling work. The decision to have Kill() delegate to SignalCore(SIGKILL) keeps the code DRY while the void return type correctly matches the "fire-and-forget" semantics of Process.Kill().

Summary: ✅ LGTM. The implementation matches the approved API shape, all prior maintainer review feedback (from @jkotas, @stephentoub, @adamsitnik) has been addressed through multiple iterations, and the code is correct and well-tested. No blocking issues found. Two minor follow-up observations below.


Detailed Findings

✅ API Approval Verification — Matches approved shape

The approved API from #125838 (comment) by @bartonjs specifies:

publicpartialclassSafeProcessHandle{publicvoidKill();publicboolSignal(PosixSignalsignal);}

The ref assembly implementation matches exactly:

  • Kill()void return ✅
  • Signal(PosixSignal signal)bool return ✅
  • Parameter name signal matches ✅
  • No extra unapproved public API ✅
  • No missing approved API from this PR's scope ✅
  • Platform attributes ([UnsupportedOSPlatform("ios")], [UnsupportedOSPlatform("tvos")], [SupportedOSPlatform("maccatalyst")]) are consistent with Start

✅ Correctness — Edge cases handled properly

Both platform implementations handle key edge cases correctly:

  • Already-exited process: Windows detects via ERROR_ACCESS_DENIED + GetExitCodeProcess returning non-STILL_ACTIVE; Unix detects via ESRCH. Both return false from SignalCore, which Kill() silently discards (matching Process.Kill semantics).
  • Unsupported signals: Windows throws PlatformNotSupportedException for non-SIGKILL (matching PosixSignalRegistration.Create behavior); Unix throws PlatformNotSupportedException when GetPlatformSignalNumber returns 0.
  • iOS/tvOS: Unix SignalCore checks ProcessUtils.PlatformDoesNotSupportProcessStartAndKill first, matching the pattern in Process.Kill().
  • Error code capture: Windows captures Marshal.GetLastWin32Error() immediately after TerminateProcess fails (before any other calls); Unix uses Interop.Sys.GetLastErrorInfo() and passes RawErrno to Win32Exception.
  • Process.Kill delegation: On Windows, Process.Kill() now correctly delegates to SafeProcessHandle.Kill(), reducing code duplication.

✅ Native Code — Clean simplification

  • SystemNative_Kill stripped to a thin wrapper around kill(pid, signal) — all PAL mapping removed since callers now pass platform-native signal numbers. This is consistent with SystemNative_EnablePosixSignalHandling and other System.Native APIs.
  • SystemNative_GetPlatformSIGSTOP() correctly placed in pal_signal.c/pal_signal.h (next to GetPlatformSignalNumber), with a WASM stub returning 0.
  • The old Signals PAL enum (PAL_NONE, PAL_SIGKILL, PAL_SIGSTOP) removed cleanly.

✅ Test Coverage — Comprehensive

Tests cover all key scenarios:

TestScenario
Kill_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Signal_InvalidHandle_ThrowsInvalidOperationExceptionError path — invalid handle
Kill_RunningProcess_TerminatesHappy path — kill via SafeProcessHandle.Start
Kill_AlreadyExited_DoesNotThrowAlready-exited process — no exception
Signal_SIGKILL_RunningProcess_ReturnsTrueSignal delivery confirmation
Signal_SIGKILL_AlreadyExited_ReturnsFalseAlready-exited returns false
Signal_NonSIGKILL_OnWindows_ThrowsPlatformNotSupportedExceptionWindows platform limitation
Signal_SIGTERM_RunningProcess_ReturnsTrueUnix signal support
Kill_HandleWithoutTerminatePermission_ThrowsWin32ExceptionWindows permissions

Existing tests also updated to use the new API (Kill_ExitedNonChildProcess_DoesNotThrow, ChildProcess_WithParentSignalHandler_CanReceiveSignals).

💡 entrypoints.c — Minor organizational note (follow-up)

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent, but this is purely organizational and not a blocker.

💡 Review feedback fully addressed

All prior review comments from @jkotas, @stephentoub, and @adamsitnik have been addressed across 9+ follow-up commits, including:

  • Removing Unix implementation details from Kill() docs (stephentoub)
  • Adding PROCESS_TERMINATE to docs + permission test (jkotas/adamsitnik)
  • Delegating Process.Kill() on Windows to SafeProcessHandle.Kill() (jkotas)
  • Adding PlatformDoesNotSupportProcessStartAndKill check (jkotas)
  • Simplifying native SystemNative_Kill to pass-through (jkotas)
  • Updating SendSignal test helper to accept Process (jkotas/adamsitnik)
  • Using short Win32Exception name in XML docs (jkotas)

Generated by Code Review for issue #126313 ·

Comment threadsrc/native/libs/System.Native/pal_signal_wasm.c
auto-merge was automatically disabled March 31, 2026 15:10

Head branch was pushed to by a user without write access

CopilotAI requested a review from jkotasMarch 31, 2026 15:12
@adamsitnik
adamsitnik enabled auto-merge (squash) March 31, 2026 15:21
@jkotas

Copy link
Copy Markdown
Member

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group between SystemNative_Kill and SystemNative_GetPid. The other signal entries (GetPlatformSignalNumber, EnablePosixSignalHandling, etc.) are at lines 286-291. Moving the entrypoint to the signal group would be more consistent

@copilot Address this feedback

auto-merge was automatically disabled March 31, 2026 15:49

Head branch was pushed to by a user without write access

CopilotAI commented Mar 31, 2026

Copy link
Copy Markdown
ContributorAuthor

SystemNative_GetPlatformSIGSTOP was moved to pal_signal.c/pal_signal.h (alongside GetPlatformSignalNumber), but its entrypoint entry in entrypoints.c (line 219) remains in the pal_process group betw...

Done in 493466c — moved DllImportEntry(SystemNative_GetPlatformSIGSTOP) from the process group (after SystemNative_Kill) to the signal group (after SystemNative_GetPlatformSignalNumber).

CopilotAI requested a review from jkotasMarch 31, 2026 15:53
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement SafeProcessHandle.Kill and Signal

5 participants

@jkotas@adamsitnik@stephentoub