Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
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
11 changes: 4 additions & 7 deletions src/Common/src/Interop/Unix/System.Native/Interop.WaitId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,14 @@ internal static partial class Interop
internal static partial class Sys
{
/// <summary>
/// Waits for terminated child processes.
/// Returns the pid of a terminated child without reaping it.
/// </summary>
/// <param name="pid">The PID of a child process. -1 for any child.</param>
/// <param name="status">The output exit status of the process</param>
/// <param name="keepWaitable">Tells the OS to leave the child waitable or reap it.</param>
/// <returns>
/// 1) returns the process id of a terminated child process
/// 2) if no children are waiting, 0 is returned
/// 2) if no children are terminated, 0 is returned
/// 3) on error, -1 is returned
/// </returns>
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitIdExitedNoHang", SetLastError = true)]
internal static extern int WaitIdExitedNoHang(int pid, out int exitCode, bool keepWaitable);
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitIdAnyExitedNoHangNoWait", SetLastError = true)]
internal static extern int WaitIdAnyExitedNoHangNoWait();
}
}
24 changes: 24 additions & 0 deletions src/Common/src/Interop/Unix/System.Native/Interop.WaitPid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Sys
{
/// <summary>
/// Reaps a terminated child.
/// </summary>
/// <returns>
/// 1) when a child is reaped, its process id is returned
/// 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.
/// </returns>
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitPidExitedNoHang", SetLastError = true)]
internal static extern int WaitPidExitedNoHang(int pid, out int exitCode);
}
}
42 changes: 27 additions & 15 deletions src/Native/Unix/System.Native/pal_process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -450,34 +450,46 @@ extern "C" void SystemNative_SysLog(SysLogPriority priority, const char* message
syslog(static_cast<int>(priority), message, arg1);
}

extern "C" int32_t SystemNative_WaitIdExitedNoHang(int32_t pid, int32_t* exitCode, int32_t keepWaitable)
extern "C" int32_t SystemNative_WaitIdAnyExitedNoHangNoWait()
{
assert(exitCode != nullptr);

siginfo_t siginfo;
int32_t result;
idtype_t idtype = pid == -1 ? P_ALL : P_PID;
int options = WEXITED | WNOHANG;
if (keepWaitable != 0)
{
options |= WNOWAIT;
}
while (CheckInterrupted(result = waitid(idtype, static_cast<id_t>(pid), &siginfo, options)));
if (idtype == P_ALL && result == -1 && errno == ECHILD)
while (CheckInterrupted(result = waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)));
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.

Did you mean to change the pid from -1 to 0? From the man page, that would seem to restrict this to only be for child processes in the same group.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

id should be ignored for P_ALL. I've changed from -1 to 0 because id_t is unsigned and it gave me a compile error if I didn't add a cast.

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.

id should be ignored for P_ALL

Where is that documented? I'm not seeing it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Linux man page: https://linux.die.net/man/3/waitid

If idtype is P_ALL, waitid() shall wait for any children and id is ignored.

http://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html has the same line.

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.

Ok, thanks.

if (result == -1 && errno == ECHILD)
{
// The calling process has no existing unwaited-for child processes.
result = 0;
}
else if (result == 0 && siginfo.si_signo == SIGCHLD)
{
if (siginfo.si_code == CLD_EXITED)
result = siginfo.si_pid;
}
return result;
}

extern "C" int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode)
{
assert(exitCode != nullptr);

int32_t result;
int status;
while (CheckInterrupted(result = waitpid(pid, &status, WNOHANG)));
if (result > 0)
{
if (WIFEXITED(status))
{
// the child terminated normally.
*exitCode = WEXITSTATUS(status);
}
else if (WIFSIGNALED(status))
{
*exitCode = siginfo.si_status;
// child process was terminated by a signal.
*exitCode = 128 + WTERMSIG(status);
}
else
{
*exitCode = 128 + siginfo.si_status;
assert(false);
}
result = siginfo.si_pid;
}
return result;
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.

Do we need all of this logic in native code? In 2.0, wasn't most of it in managed and we just P/Invoked for waitpid, WIFEXITED, etc.? We've been trying to keep the shims as thin as possible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, 2.0 had pinvoke for WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG.
Those were not needed for waitid but now needed again. Personally I like having this packed together with the waitpid call and avoid the back-and-forth between native and managed. If you want, I can add back those functions.. Here or in a follow-up PR.

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.

Ok. We can keep it here in this PR, but I do think it'd be good to revisit it subsequently. I think we've been a bit lax about the intent of the shims and we've ended up with more native code than is desirable.

}
Expand Down
16 changes: 13 additions & 3 deletions src/Native/Unix/System.Native/pal_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,23 @@ DLLEXPORT int32_t SystemNative_GetSid(int32_t pid);
DLLEXPORT void SystemNative_SysLog(SysLogPriority priority, const char* message, const char* arg1);

/**
* Waits for terminated child processes.
* Returns the pid of a terminated child without reaping it.
*
* 1) returns the process id of a terminated child process
* 2) if no children are waiting, 0 is returned
* 2) if no children are terminated, 0 is returned
* 3) on error, -1 is returned
*/
DLLEXPORT int32_t SystemNative_WaitIdExitedNoHang(int32_t pid, int32_t* exitCode, int32_t keepWaitable);
DLLEXPORT extern "C" int32_t SystemNative_WaitIdAnyExitedNoHangNoWait();

/**
* Reaps a terminated child.
*
* 1) when a child is reaped, its process id is returned
* 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.
*/
DLLEXPORT extern "C" int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode);

/**
* Gets the configurable limit or variable for system path or file descriptor options.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.WaitId.cs">
<Link>Common\Interop\Unix\Interop.WaitId.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.WaitPid.cs">
<Link>Common\Interop\Unix\Interop.WaitPid.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup Condition=" '$(TargetsLinux)' == 'true'">
<Compile Include="System\Diagnostics\Process.Linux.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ private bool TryReapChild()

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

if (waitResult == _processId)
{
Expand Down Expand Up @@ -563,8 +563,7 @@ internal static void CheckChildren(bool reapAll)
do
{
// Find a process that terminated without reaping it yet.
int exitCode;
pid = Interop.Sys.WaitIdExitedNoHang(-1, out exitCode, keepWaitable: true);
pid = Interop.Sys.WaitIdAnyExitedNoHangNoWait();
if (pid > 0)
{
if (s_childProcessWaitStates.TryGetValue(pid, out ProcessWaitState pws))
Expand Down Expand Up @@ -638,7 +637,7 @@ internal static void CheckChildren(bool reapAll)
do
{
int exitCode;
pid = Interop.Sys.WaitIdExitedNoHang(-1, out exitCode, keepWaitable: false);
pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode);
} while (pid > 0);
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/System.Diagnostics.Process/tests/ProcessTests.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,21 @@ public void TestStartWithNonExistingUserThrows()
Assert.Throws<Win32Exception>(() => p.Start());
}

[Fact]
public void TestExitCodeKilledChild()
{
using (Process p = CreateProcessLong())
{
p.Start();
p.Kill();
p.WaitForExit();

// SIGKILL may change per platform
const int SIGKILL = 9; // Linux, macOS, FreeBSD, ...
Assert.Equal(128 + SIGKILL, p.ExitCode);
}
}

/// <summary>
/// Tests when running as a normal user and starting a new process as the same user
/// works as expected.
Expand Down