Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 484f904

Browse files
joperezrstephentoub
authored andcommitted
Fix Process.ExitCode on mac for killed processes (#29407) (#29445)
* Fix Process.ExitCode on mac for killed processes #26291 changed process reaping from using waitpid to waitid. This caused a regression on mac, since for processes that are killed, (on mac) waitid does not return the signal number that caused the process to terminated. We change back to waitpid for reaping children and determining the exit code. waitid is used to find terminated children. Fixes https://github.com/dotnet/corefx/issues/29370 * TestExitCodeKilledChild: remove runtime check * TestExitCodeKilledChild: remove greater than assert
1 parent 5820819 commit 484f904

File tree

7 files changed

+89
-29
lines changed

7 files changed

+89
-29
lines changed

src/Common/src/Interop/Unix/System.Native/Interop.WaitId.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,14 @@ internal static partial class Interop
1010
internal static partial class Sys
1111
{
1212
/// <summary>
13-
/// Waits for terminated child processes.
13+
/// Returns the pid of a terminated child without reaping it.
1414
/// </summary>
15-
/// <param name="pid">The PID of a child process. -1 for any child.</param>
16-
/// <param name="status">The output exit status of the process</param>
17-
/// <param name="keepWaitable">Tells the OS to leave the child waitable or reap it.</param>
1815
/// <returns>
1916
/// 1) returns the process id of a terminated child process
20-
/// 2) if no children are waiting, 0 is returned
17+
/// 2) if no children are terminated, 0 is returned
2118
/// 3) on error, -1 is returned
2219
/// </returns>
23-
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitIdExitedNoHang", SetLastError = true)]
24-
internal static extern int WaitIdExitedNoHang(int pid, out int exitCode, bool keepWaitable);
20+
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitIdAnyExitedNoHangNoWait", SetLastError = true)]
21+
internal static extern int WaitIdAnyExitedNoHangNoWait();
2522
}
2623
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Runtime.InteropServices;
7+
8+
internal static partial class Interop
9+
{
10+
internal static partial class Sys
11+
{
12+
/// <summary>
13+
/// Reaps a terminated child.
14+
/// </summary>
15+
/// <returns>
16+
/// 1) when a child is reaped, its process id is returned
17+
/// 2) if pid is not a child or there are no unwaited-for children, -1 is returned (errno=ECHILD)
18+
/// 3) if the child has not yet terminated, 0 is returned
19+
/// 4) on error, -1 is returned.
20+
/// </returns>
21+
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_WaitPidExitedNoHang", SetLastError = true)]
22+
internal static extern int WaitPidExitedNoHang(int pid, out int exitCode);
23+
}
24+
}

src/Native/Unix/System.Native/pal_process.cpp

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -450,34 +450,46 @@ extern "C" void SystemNative_SysLog(SysLogPriority priority, const char* message
450450
syslog(static_cast<int>(priority), message, arg1);
451451
}
452452

453-
extern "C" int32_t SystemNative_WaitIdExitedNoHang(int32_t pid, int32_t* exitCode, int32_t keepWaitable)
453+
extern "C" int32_t SystemNative_WaitIdAnyExitedNoHangNoWait()
454454
{
455-
assert(exitCode != nullptr);
456-
457455
siginfo_t siginfo;
458456
int32_t result;
459-
idtype_t idtype = pid == -1 ? P_ALL : P_PID;
460-
int options = WEXITED | WNOHANG;
461-
if (keepWaitable != 0)
462-
{
463-
options |= WNOWAIT;
464-
}
465-
while (CheckInterrupted(result = waitid(idtype, static_cast<id_t>(pid), &siginfo, options)));
466-
if (idtype == P_ALL && result == -1 && errno == ECHILD)
457+
while (CheckInterrupted(result = waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)));
458+
if (result == -1 && errno == ECHILD)
467459
{
460+
// The calling process has no existing unwaited-for child processes.
468461
result = 0;
469462
}
470463
else if (result == 0 && siginfo.si_signo == SIGCHLD)
471464
{
472-
if (siginfo.si_code == CLD_EXITED)
465+
result = siginfo.si_pid;
466+
}
467+
return result;
468+
}
469+
470+
extern "C" int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode)
471+
{
472+
assert(exitCode != nullptr);
473+
474+
int32_t result;
475+
int status;
476+
while (CheckInterrupted(result = waitpid(pid, &status, WNOHANG)));
477+
if (result > 0)
478+
{
479+
if (WIFEXITED(status))
480+
{
481+
// the child terminated normally.
482+
*exitCode = WEXITSTATUS(status);
483+
}
484+
else if (WIFSIGNALED(status))
473485
{
474-
*exitCode = siginfo.si_status;
486+
// child process was terminated by a signal.
487+
*exitCode = 128 + WTERMSIG(status);
475488
}
476489
else
477490
{
478-
*exitCode = 128 + siginfo.si_status;
491+
assert(false);
479492
}
480-
result = siginfo.si_pid;
481493
}
482494
return result;
483495
}

src/Native/Unix/System.Native/pal_process.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,23 @@ extern "C" int32_t SystemNative_GetSid(int32_t pid);
206206
extern "C" void SystemNative_SysLog(SysLogPriority priority, const char* message, const char* arg1);
207207

208208
/**
209-
* Waits for terminated child processes.
209+
* Returns the pid of a terminated child without reaping it.
210210
*
211211
* 1) returns the process id of a terminated child process
212-
* 2) if no children are waiting, 0 is returned
212+
* 2) if no children are terminated, 0 is returned
213213
* 3) on error, -1 is returned
214214
*/
215-
extern "C" int32_t SystemNative_WaitIdExitedNoHang(int32_t pid, int32_t* exitCode, int32_t keepWaitable);
215+
extern "C" int32_t SystemNative_WaitIdAnyExitedNoHangNoWait();
216+
217+
/**
218+
* Reaps a terminated child.
219+
*
220+
* 1) when a child is reaped, its process id is returned
221+
* 2) if pid is not a child or there are no unwaited-for children, -1 is returned (errno=ECHILD)
222+
* 3) if the child has not yet terminated, 0 is returned
223+
* 4) on error, -1 is returned.
224+
*/
225+
extern "C" int32_t SystemNative_WaitPidExitedNoHang(int32_t pid, int32_t* exitCode);
216226

217227
/**
218228
* Gets the configurable limit or variable for system path or file descriptor options.

src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,9 @@
351351
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.WaitId.cs">
352352
<Link>Common\Interop\Unix\Interop.WaitId.cs</Link>
353353
</Compile>
354+
<Compile Include="$(CommonPath)\Interop\Unix\System.Native\Interop.WaitPid.cs">
355+
<Link>Common\Interop\Unix\Interop.WaitPid.cs</Link>
356+
</Compile>
354357
</ItemGroup>
355358
<ItemGroup Condition=" '$(TargetsLinux)' == 'true'">
356359
<Compile Include="System\Diagnostics\Process.Linux.cs" />

src/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ private bool TryReapChild()
527527

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

532532
if (waitResult == _processId)
533533
{
@@ -563,8 +563,7 @@ internal static void CheckChildren(bool reapAll)
563563
do
564564
{
565565
// Find a process that terminated without reaping it yet.
566-
int exitCode;
567-
pid = Interop.Sys.WaitIdExitedNoHang(-1, out exitCode, keepWaitable: true);
566+
pid = Interop.Sys.WaitIdAnyExitedNoHangNoWait();
568567
if (pid > 0)
569568
{
570569
if (s_childProcessWaitStates.TryGetValue(pid, out ProcessWaitState pws))
@@ -638,7 +637,7 @@ internal static void CheckChildren(bool reapAll)
638637
do
639638
{
640639
int exitCode;
641-
pid = Interop.Sys.WaitIdExitedNoHang(-1, out exitCode, keepWaitable: false);
640+
pid = Interop.Sys.WaitPidExitedNoHang(-1, out exitCode);
642641
} while (pid > 0);
643642
}
644643
}

src/System.Diagnostics.Process/tests/ProcessTests.Unix.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,21 @@ public void TestStartWithNonExistingUserThrows()
387387
Assert.Throws<Win32Exception>(() => p.Start());
388388
}
389389

390+
[Fact]
391+
public void TestExitCodeKilledChild()
392+
{
393+
using (Process p = CreateProcessLong())
394+
{
395+
p.Start();
396+
p.Kill();
397+
p.WaitForExit();
398+
399+
// SIGKILL may change per platform
400+
const int SIGKILL = 9; // Linux, macOS, FreeBSD, ...
401+
Assert.Equal(128 + SIGKILL, p.ExitCode);
402+
}
403+
}
404+
390405
/// <summary>
391406
/// Tests when running as a normal user and starting a new process as the same user
392407
/// works as expected.

0 commit comments

Comments
 (0)