Skip to content

Add new process management APIs to SafeProcessHandle#124375

Open
Copilot wants to merge 59 commits intomainfrom
copilot/add-new-apis-to-safeprocesshandle
Open

Add new process management APIs to SafeProcessHandle#124375
Copilot wants to merge 59 commits intomainfrom
copilot/add-new-apis-to-safeprocesshandle

Conversation

Copy link
Contributor

Copilot AI commented Feb 13, 2026

Description

Implements the approved API surface for SafeProcessHandle (#123380): process lifecycle management via Start, Open, Kill, Signal, SignalProcessGroup, and WaitForExit overloads. Windows implementation complete; Unix stubs throw NotImplementedException (separate PR).

ProcessStartOptions options = new("cmd.exe") { Arguments = { "/c", "echo hello" } };
using SafeProcessHandle handle = SafeProcessHandle.Start(options, input: null, output: null, error: null);
ProcessExitStatus status = handle.WaitForExit();
// status.ExitCode == 0, status.Canceled == false

Public API

  • Open(int processId) — opens existing process handle
  • Start(ProcessStartOptions, SafeFileHandle?, SafeFileHandle?, SafeFileHandle?) — create process with explicit stdio handles
  • Kill() — terminate process
  • Signal(PosixSignal) / SignalProcessGroup(PosixSignal) — send signal; accepts raw signal numbers like PosixSignalRegistration.Create
  • WaitForExit(), TryWaitForExit(TimeSpan, out ProcessExitStatus?), WaitForExitOrKillOnTimeout(TimeSpan) — synchronous wait
  • WaitForExitAsync(CancellationToken), WaitForExitOrKillOnCancellationAsync(CancellationToken) — async wait

Internal for now (pending follow-up): ProcessId, StartSuspended, Resume, KillProcessGroup.

Windows implementation

  • STARTUPINFOEX + PROC_THREAD_ATTRIBUTE_HANDLE_LIST for explicit handle inheritance
  • Job objects for KillOnParentExit and process group termination (best-effort emulation of Unix process groups)
  • DangerousAddRef/DangerousRelease on InheritedHandles in PrepareHandleAllowList and on this in KillCore to prevent use-after-free races with concurrent Dispose
  • Interlocked.Exchange for _threadHandle and _processGroupJobHandle in ReleaseHandle; Volatile.Read for _processGroupJobHandle access elsewhere
  • Sync wait methods use WaitForSingleObject(SafeProcessHandle, ...) directly — no handle duplication via ProcessWaitHandle
  • Async wait methods retain ProcessWaitHandle for RegisterWaitForSingleObject
  • NativeMemory.Alloc with two-arg overflow-safe version; typed pointers (IntPtr*, void*)
  • OOM-safe handle cleanup in finally; error check immediately after CreateProcess

Architecture — ProcessUtils as shared dependency

  • ProcessUtils.cs holds s_processStartLock (ReaderWriterLockSlim) — avoids ProcessSafeProcessHandle dependency cycle
  • ProcessUtils.Windows.cs holds BuildArgs(string, IList<string>?) and GetEnvironmentVariablesBlock
  • Process.Windows.cs acquires write lock; SafeProcessHandle.Windows.cs acquires read lock

Interop additions

  • Interop.JobObjects.cs, Interop.ProcThreadAttributeList.cs, Interop.ConsoleCtrl.cs (consolidated), Interop.HandleInformation.cs (extended), Interop.ResumeThread_IntPtr.cs, Interop.WaitForSingleObject_SafeProcessHandle.cs

Tests

  • SafeProcessHandleTests.cs — start/kill/wait/timeout/signal tests with Nano Server support (ping fallback); signal tests as [Theory] with [InlineData]

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 22 commits February 11, 2026 13:33
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…, Theory for utils

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…h for sh

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…syntax

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
… remove dead code

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…names

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Feb 13, 2026
Copilot AI and others added 3 commits February 13, 2026 12:24
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…s, and Windows implementation

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI changed the title [WIP] Add new APIs to SafeProcessHandle class Add new process management APIs to SafeProcessHandle Feb 13, 2026
}
}

internal static int GetTimeoutInMilliseconds(TimeSpan timeout)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
internal static int GetTimeoutInMilliseconds(TimeSpan timeout)
internal static int ToTimeoutInMilliseconds(TimeSpan timeout)

This method is called ToTimeoutMilliseconds in other places.

We have identical multiple copies of this method. It is candidate for a helper API.

Copy link
Member

Choose a reason for hiding this comment

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

Do we actually need to convert the TimeSpan to int here? There are WaitHandle overloads that take TimeSpan. Can we just call those instead?

Copy link
Member

Choose a reason for hiding this comment

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

Do we actually need to convert the TimeSpan to int here? There are WaitHandle overloads that take TimeSpan.

You are right, on Windows I could just pass TimeSpan to the Core implementation, but on Unix I need raw int milliseconds to pass it to poll and similar sys-calls.


private void SendSignalCore(PosixSignal signal)
{
if (signal == PosixSignal.SIGKILL)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Would be simpler to just throw PlatformNotSupportedException from this method unconditionally on Windows, and mark this method as unsupported on Windows? I expect that killing the process is typically going to be done by the Kill API. Handling PosixSignal.SIGKILL here does not have a lot of value.

Copy link
Member

@adamsitnik adamsitnik Mar 17, 2026

Choose a reason for hiding this comment

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

Handling PosixSignal.SIGKILL here does not have a lot of value.

I agree it's not a lot, but I can imagine that some users with Unix background would prefer using Signal(PosixSignal.SIGKILL) over Kill.

It's not something that I have very strong opinion about.

@jkotas
Copy link
Member

jkotas commented Mar 17, 2026

LGTM modulo feedback

- don't swallow kill exceptions by WaitForExitOrKill* methods
- rename helper method
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants