Files
Peter Steinberger 1257279de6 fix(macos): suppress SIGPIPE on process pipe write ends (#127666)
Writing to a pipe whose reader has already exited raises SIGPIPE, which
kills the whole process instead of throwing. MacNodeHostWorker already
guarded its stdin pipe with F_SETNOSIGPIPE; the codex App Server client
and the MLX TTS helper transport did not, so a child exiting mid-write
could take down the app. Suppressing the signal exposed that an
undelivered App Server request write was reported as appServerUnavailable
even though the frame was provably never sent, so it now requeues once
onto a fresh child instead of failing the caller.

Test-side pipe write ends whose readers are spawned children (or a
readability handler that can close the pipe mid-test) get the same
suppression so a racing reader exit fails the assertion instead of
killing swiftpm-testing-helper with signal 13, which is what caused the
macos-swift CI lane's intermittent unrelated-test crashes (e.g. PR
#126559, run 32341197738 job 96340683947).
2026-08-21 15:40:50 -07:00

38 lines
1.4 KiB
Swift

import Darwin
import Foundation
extension FileHandle {
/// Marks a pipe/socket write end so a vanished reader fails the write with a
/// thrown EPIPE instead of raising SIGPIPE, which kills the whole process.
/// Required on every write end whose reader is another process that can exit.
@discardableResult
func disableSIGPIPE() -> Bool {
fcntl(self.fileDescriptor, F_SETNOSIGPIPE, 1) != -1
}
/// Reads until EOF using the throwing FileHandle API and returns empty `Data` on failure.
///
/// Important: Avoid legacy, non-throwing FileHandle read APIs (e.g. `readDataToEndOfFile()` and
/// `availableData`). They can raise Objective-C exceptions when the handle is closed/invalid, which
/// will abort the process.
func readToEndSafely() -> Data {
do {
return try self.readToEnd() ?? Data()
} catch {
return Data()
}
}
/// Reads up to `count` bytes using the throwing FileHandle API and returns empty `Data` on failure/EOF.
///
/// Important: Use this instead of `availableData` in callbacks like `readabilityHandler` to avoid
/// Objective-C exceptions terminating the process.
func readSafely(upToCount count: Int) -> Data {
do {
return try self.read(upToCount: count) ?? Data()
} catch {
return Data()
}
}
}