Skip to content

Commit c8a88cd

Browse files
authored
Fix a Linux bug where an error during waitid() could cause us to miss a continuation. (#369)
The logic for exit test handling currently calls `waitid()` but doesn't loop on error (`EINTR` or `ECHILD`) so it could cause a missed continuation in corner cases. This PR refactors (_again_) the relevant code so that it loops and is shared with the Darwin/dispatch-source-based implementation. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 6bf7934 commit c8a88cd

File tree

1 file changed

+26
-29
lines changed

1 file changed

+26
-29
lines changed

Sources/Testing/ExitTests/WaitFor.swift

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,29 @@
1212
internal import TestingInternals
1313

1414
#if SWT_TARGET_OS_APPLE || os(Linux)
15-
extension ExitCondition {
16-
/// Initialize an instance of this type from an instance of the POSIX
17-
/// `siginfo_t` type.
18-
///
19-
/// - Parameters:
20-
/// - siginfo: The instance of `siginfo_t` to initialize from.
21-
///
22-
/// - Throws: If `siginfo.si_code` does not equal either `CLD_EXITED`,
23-
/// `CLD_KILLED`, or `CLD_DUMPED` (i.e. it does not represent an exit
24-
/// condition.)
25-
fileprivate init(_ siginfo: siginfo_t) throws {
26-
switch siginfo.si_code {
27-
case .init(CLD_EXITED):
28-
self = .exitCode(siginfo.si_status)
29-
case .init(CLD_KILLED), .init(CLD_DUMPED):
30-
self = .signal(siginfo.si_status)
31-
default:
32-
throw SystemError(description: "Unexpected siginfo_t value. Please file a bug report at https://github.com/apple/swift-testing/issues/new and include this information: \(String(reflecting: siginfo))")
15+
/// Block the calling thread, wait for the target process to exit, and return
16+
/// a value describing the conditions under which it exited.
17+
///
18+
/// - Parameters:
19+
/// - pid: The ID of the process to wait for.
20+
///
21+
/// - Throws: If the exit status of the process with ID `pid` cannot be
22+
/// determined (i.e. it does not represent an exit condition.)
23+
fileprivate func _blockAndWait(for pid: pid_t) throws -> ExitCondition {
24+
// Get the exit status of the process or throw an error (other than EINTR.)
25+
while true {
26+
var siginfo = siginfo_t()
27+
if 0 == waitid(P_PID, id_t(pid), &siginfo, WEXITED) {
28+
switch siginfo.si_code {
29+
case .init(CLD_EXITED):
30+
return .exitCode(siginfo.si_status)
31+
case .init(CLD_KILLED), .init(CLD_DUMPED):
32+
return .signal(siginfo.si_status)
33+
default:
34+
throw SystemError(description: "Unexpected siginfo_t value. Please file a bug report at https://github.com/apple/swift-testing/issues/new and include this information: \(String(reflecting: siginfo))")
35+
}
36+
} else if case let errorCode = swt_errno(), errorCode != EINTR {
37+
throw CError(rawValue: errorCode)
3338
}
3439
}
3540
}
@@ -64,9 +69,9 @@ private let _createWaitThreadImpl: Void = {
6469
// and pass the resulting exit condition back to the calling task. If
6570
// there is no continuation, then either it hasn't been stored yet or
6671
// this child process is not tracked by the waiter thread.
67-
if let continuation, 0 == waitid(P_PID, id_t(pid), &siginfo, WEXITED) {
72+
if let continuation {
6873
let result = Result {
69-
try ExitCondition(siginfo)
74+
try _blockAndWait(for: pid)
7075
}
7176
continuation.resume(with: result)
7277
}
@@ -145,15 +150,7 @@ func wait(for pid: pid_t) async throws -> ExitCondition {
145150
}
146151
withExtendedLifetime(source) {}
147152

148-
// Get the exit status of the process or throw an error (other than EINTR.)
149-
while true {
150-
var siginfo = siginfo_t()
151-
if 0 == waitid(P_PID, id_t(pid), &siginfo, WEXITED) {
152-
return try ExitCondition(siginfo)
153-
} else if case let errorCode = swt_errno(), errorCode != EINTR {
154-
throw CError(rawValue: errorCode)
155-
}
156-
}
153+
return try _blockAndWait(for: pid)
157154
#else
158155
// Ensure the waiter thread is running.
159156
_createWaitThread()

0 commit comments

Comments
 (0)