Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ internal static partial class Sys
/// 4) on error, -1 is returned.
/// </returns>
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitPidExitedNoHang", SetLastError = true)]
internal static partial int WaitPidExitedNoHang(int pid, out int exitCode);
internal static partial int WaitPidExitedNoHang(int pid, out int exitCode, out int terminatingSignal);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,11 @@ public ProcessModule? MainModule
/// <summary>Checks whether the process has exited and updates state accordingly.</summary>
private void UpdateHasExited()
{
int? exitCode;
_exited = GetWaitState().GetExited(out exitCode, refresh: true);
if (_exited && exitCode != null)
ProcessExitStatus? exitStatus;
_exited = GetWaitState().GetExited(out exitStatus, refresh: true);
if (_exited && exitStatus is not null)
{
_exitCode = exitCode.Value;
_exitCode = exitStatus.ExitCode;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ internal void ReleaseRef()

/// <summary>Whether the associated process exited.</summary>
private bool _exited;
/// <summary>If the process exited, it's exit code, or null if we were unable to determine one.</summary>
private int? _exitCode;
/// <summary>If the process exited, its exit status, or null if we were unable to determine one.</summary>
private ProcessExitStatus? _exitStatus;
/// <summary>
/// The approximate time the process exited. We do not have the ability to know exact time a process
/// exited, so we approximate it by storing the time that we discovered it exited.
Expand Down Expand Up @@ -310,22 +310,22 @@ internal bool HasExited
}
}

internal bool GetExited(out int? exitCode, bool refresh)
internal bool GetExited(out ProcessExitStatus? exitStatus, bool refresh)
{
lock (_gate)
{
// Have we already exited? If so, return the cached results.
if (_exited)
{
exitCode = _exitCode;
exitStatus = _exitStatus;
return true;
}

// Is another wait operation in progress? If so, then we haven't exited,
// and that task owns the right to call CheckForNonChildExit.
if (!_waitInProgress.IsCompleted)
{
exitCode = null;
exitStatus = null;
return false;
}

Expand All @@ -338,7 +338,7 @@ internal bool GetExited(out int? exitCode, bool refresh)

// We now have an up-to-date snapshot for whether we've exited,
// and if we have, what the exit code is (if we were able to find out).
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

This comment still refers to caching the "exit code", but the method now tracks ProcessExitStatus?. Update the wording to reflect that we cache the exit status (and that it may be null when it can't be determined).

Suggested change
// and if we have, what the exit code is (if we were able to find out).
// and if we have, what the exit status is (which may be null if it couldn't be determined).

Copilot uses AI. Check for mistakes.
exitCode = _exitCode;
exitStatus = _exitStatus;
return _exited;
}
}
Expand Down Expand Up @@ -539,13 +539,14 @@ private async Task WaitForExitAsync(CancellationToken cancellationToken)
}
}

private void ChildReaped(int exitCode, bool configureConsole)
private void ChildReaped(int exitCode, int terminatingSignal, bool configureConsole)
{
lock (_gate)
{
Debug.Assert(!_exited);

_exitCode = exitCode;
PosixSignal? signal = terminatingSignal != 0 ? (PosixSignal)terminatingSignal : null;
_exitStatus = new ProcessExitStatus(exitCode, canceled: false, signal);

if (_usesTerminal)
{
Expand All @@ -568,11 +569,12 @@ private bool TryReapChild(bool configureConsole)

// Try to get the state of the child process
int exitCode;
int waitResult = Interop.Sys.WaitPidExitedNoHang(_processId, out exitCode);
int terminatingSignal;
int waitResult = Interop.Sys.WaitPidExitedNoHang(_processId, out exitCode, out terminatingSignal);

if (waitResult == _processId)
{
ChildReaped(exitCode, configureConsole);
ChildReaped(exitCode, terminatingSignal, configureConsole);
return true;
}
else if (waitResult == 0)
Expand Down Expand Up @@ -673,7 +675,8 @@ internal static void CheckChildren(bool reapAll, bool configureConsole)
do
{
int exitCode;
pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode);
int terminatingSignal;
pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode, out terminatingSignal);
if (pid <= 0)
{
break;
Expand All @@ -682,7 +685,7 @@ internal static void CheckChildren(bool reapAll, bool configureConsole)
// Check if the process is a child that has just terminated.
if (s_childProcessWaitStates.TryGetValue(pid, out ProcessWaitState? pws))
{
pws.ChildReaped(exitCode, configureConsole);
pws.ChildReaped(exitCode, terminatingSignal, configureConsole);
pws.ReleaseRef();
}
} while (true);
Expand Down
11 changes: 9 additions & 2 deletions src/native/libs/System.Native/pal_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "pal_config.h"
#include "pal_process.h"
#include "pal_signal.h"
#include "pal_io.h"
#include "pal_utilities.h"

Expand Down Expand Up @@ -929,9 +930,10 @@ int32_t SystemNative_WaitIdAnyExitedNoHangNoWait(void)
return result;
}

int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode)
int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode, int32_t* terminatingSignal)
{
assert(exitCode != NULL);
assert(terminatingSignal != NULL);

int32_t result;
int status;
Expand All @@ -942,11 +944,16 @@ int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode)
{
// the child terminated normally.
*exitCode = WEXITSTATUS(status);
*terminatingSignal = 0;
}
else if (WIFSIGNALED(status))
{
// child process was terminated by a signal.
*exitCode = 128 + WTERMSIG(status);
int sig = WTERMSIG(status);
*exitCode = 128 + sig;
PosixSignal posixSignal = PosixSignalInvalid;
TryConvertSignalCodeToPosixSignal(sig, &posixSignal);
*terminatingSignal = (int32_t)posixSignal;
}
else
{
Expand Down
6 changes: 5 additions & 1 deletion src/native/libs/System.Native/pal_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,12 @@ PALEXPORT int32_t SystemNative_WaitIdAnyExitedNoHangNoWait(void);
* 2) if pid is not a child or there are no unwaited-for children, -1 is returned (errno=ECHILD)
* 3) if the child has not yet terminated, 0 is returned
* 4) on error, -1 is returned.
*
* exitCode: set to WEXITSTATUS on normal exit, or 128 + signal number on signal termination.
* terminatingSignal: set to 0 on normal exit, or the PosixSignal value on signal termination.
* For signals not in the known PosixSignal set, this is the raw signal number.
*/
PALEXPORT int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode);
PALEXPORT int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode, int32_t* terminatingSignal);

/**
* Gets the configurable limit or variable for system path or file descriptor options.
Expand Down
2 changes: 1 addition & 1 deletion src/native/libs/System.Native/pal_process_wasi.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ int32_t SystemNative_WaitIdAnyExitedNoHangNoWait(void)
return -1;
}

int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode)
int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode, int32_t* terminatingSignal)
{
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

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

SystemNative_WaitPidExitedNoHang returns -1 here without initializing exitCode / terminatingSignal (and without setting errno). Since these are out parameters, managed marshalling will still read back whatever is in those stack locations, which can lead to nondeterministic values if this stub is ever invoked. Initialize both outputs (e.g., to 0) and set errno = ENOTSUP (or the appropriate error) before returning.

Suggested change
{
{
assert(exitCode != NULL);
assert(terminatingSignal != NULL);
*exitCode = 0;
*terminatingSignal = 0;
errno = ENOTSUP;

Copilot uses AI. Check for mistakes.
return -1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/native/libs/System.Native/pal_signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ static bool IsSigIgn(struct sigaction* action)
action->sa_handler == SIG_IGN;
}

static bool TryConvertSignalCodeToPosixSignal(int signalCode, PosixSignal* posixSignal)
bool TryConvertSignalCodeToPosixSignal(int signalCode, PosixSignal* posixSignal)
{
assert(posixSignal != NULL);

Expand Down
8 changes: 8 additions & 0 deletions src/native/libs/System.Native/pal_signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ PALEXPORT void SystemNative_DisablePosixSignalHandling(int signalCode);
*/
PALEXPORT void SystemNative_HandleNonCanceledPosixSignal(int32_t signalCode);

/**
* Converts a native signal code to PosixSignal.
*
* Returns true if the signal code was mapped to a known PosixSignal.
* Returns false if the signal is not in the known set (posixSignal is set to the raw signal code).
*/
bool TryConvertSignalCodeToPosixSignal(int signalCode, PosixSignal* posixSignal);

typedef void (*ConsoleSigTtouHandler)(void);

/**
Expand Down
Loading