Skip to content

Spawn cat in its own session in testSuspendResumeProcess()#308

Merged
iCharlesHu merged 1 commit into
swiftlang:mainfrom
broken-circle:testSuspendResumeProcess-cat-session
Jun 11, 2026
Merged

Spawn cat in its own session in testSuspendResumeProcess()#308
iCharlesHu merged 1 commit into
swiftlang:mainfrom
broken-circle:testSuspendResumeProcess-cat-session

Conversation

@broken-circle

Copy link
Copy Markdown
Member

testSuspendResumeProcess() intermittently terminates the entire test process with SIGHUP under the full suite, surfacing as a bare signal-1 exit with no recorded test failure. I found this during a failed CI run for unrelated changes in #261.

error: Process '.../swift-subprocessPackageTests --testing-library swift-testing' exited with unexpected signal code 1

The test spawns /bin/cat with default PlatformOptions, so cat inherits the test runner's process group, then sends it SIGSTOP, leaving a stopped process in the runner's own process group.

On POSIX, when a process group becomes orphaned and any member is stopped, SIGHUP followed by SIGCONT is sent to every process in the group. Per the POSIX _Exit docs:

If the exit of the process causes a process group to become orphaned, and if any member of the newly-orphaned process group is stopped, then a SIGHUP signal followed by a SIGCONT signal shall be sent to each process in the newly-orphaned process group.

Under the full suite's concurrent process churn, that group intermittently became orphaned while cat was stopped, and the kernel delivered SIGHUP to every member of the group, including the test process, which exited on the signal. This surfaced only when the suite ran in parallel.

This PR spawns cat with createSession so it runs in its own session, and stopping it no longer leaves a stopped member in the runner's group. cat's own group is orphaned at setsid() time, which POSIX exempts from the SIGHUP, so the suspend/resume assertions are unaffected:

It is possible for a process group to be orphaned by a call to setpgid() or setsid(), as well as by process termination. This volume of POSIX.1-2017 does not require sending SIGHUP and SIGCONT in those cases, because, unlike process termination, those cases are not caused accidentally by applications that are unaware of job control.

processGroupID would also work here, but using createSession matches measureCancelledTeardown().

Tested this across 30 iterations of the full suite on a macOS GitHub Actions runner where it previously failed.

The test spawned `/bin/cat` with default `PlatformOptions`, so `cat`
inherited the test runner's process group, then sent it `SIGSTOP`,
leaving a stopped process in the runner's own process group.

On POSIX, when a process group becomes orphaned and any member is
stopped, `SIGHUP` followed by `SIGCONT` is sent to every process in the
group. Under the full suite's concurrent process churn, that group
intermittently became orphaned while `cat` was stopped, and the kernel
delivered `SIGHUP` to every member of the group, including the test
process, which exited on the signal. This surfaced only when the suite
ran in parallel, as a bare signal-1 exit with no recorded test failure.

Spawn `cat` with `createSession` so it runs in its own session, and
stopping it no longer leaves a stopped member in the runner's group.
`cat`'s own group is orphaned at `setsid` time, which POSIX exempts
from the SIGHUP, so the suspend/resume assertions are unaffected.
@iCharlesHu iCharlesHu merged commit eedcd95 into swiftlang:main Jun 11, 2026
46 checks passed
@broken-circle broken-circle deleted the testSuspendResumeProcess-cat-session branch June 11, 2026 14:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants