fix(macos): drain subprocess pipes before wait (#1081)

Thanks @thesash.

Co-authored-by: Sash Catanzarite <sashcatanzarite@Sash-MacBook-Pro-14in-3.local>
This commit is contained in:
Peter Steinberger
2026-01-17 08:24:34 +00:00
parent 837eea4ebd
commit eef3df9fa5
5 changed files with 14 additions and 7 deletions

View File

@@ -44,6 +44,7 @@
- Plugins: add zip installs and `--link` to avoid copying local paths. - Plugins: add zip installs and `--link` to avoid copying local paths.
### Fixes ### Fixes
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
- Telegram: accept tg/group/telegram prefixes + topic targets for inline button validation. (#1072) — thanks @danielz1z. - Telegram: accept tg/group/telegram prefixes + topic targets for inline button validation. (#1072) — thanks @danielz1z.
- Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt. - Sub-agents: normalize announce delivery origin + queue bucketing by accountId to keep multi-account routing stable. (#1061, #1058) — thanks @adam91holt.
- Sessions: include deliveryContext in sessions.list and reuse normalized delivery routing for announce/restart fallbacks. (#1058) - Sessions: include deliveryContext in sessions.list and reuse normalized delivery routing for announce/restart fallbacks. (#1058)

View File

@@ -279,6 +279,8 @@ enum GatewayEnvironment {
process.standardError = pipe process.standardError = pipe
do { do {
try process.run() try process.run()
// Read pipe before waitUntilExit to avoid potential deadlock
let data = pipe.fileHandleForReading.readToEndSafely()
process.waitUntilExit() process.waitUntilExit()
let elapsedMs = Int(Date().timeIntervalSince(start) * 1000) let elapsedMs = Int(Date().timeIntervalSince(start) * 1000)
if elapsedMs > 500 { if elapsedMs > 500 {
@@ -294,7 +296,6 @@ enum GatewayEnvironment {
bin=\(binary, privacy: .public) bin=\(binary, privacy: .public)
""") """)
} }
let data = pipe.fileHandleForReading.readToEndSafely()
let raw = String(data: data, encoding: .utf8)? let raw = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)
return Semver.parse(raw) return Semver.parse(raw)

View File

@@ -17,8 +17,11 @@ enum Launchctl {
process.standardError = pipe process.standardError = pipe
do { do {
try process.run() try process.run()
process.waitUntilExit() // Read pipe output BEFORE waitUntilExit to avoid deadlock.
// If the process writes enough to fill the pipe buffer (~64KB),
// it will block until someone reads. Reading first prevents this.
let data = pipe.fileHandleForReading.readToEndSafely() let data = pipe.fileHandleForReading.readToEndSafely()
process.waitUntilExit()
let output = String(data: data, encoding: .utf8) ?? "" let output = String(data: data, encoding: .utf8) ?? ""
return Result(status: process.terminationStatus, output: output) return Result(status: process.terminationStatus, output: output)
} catch { } catch {

View File

@@ -204,14 +204,15 @@ actor PortGuardian {
proc.standardError = Pipe() proc.standardError = Pipe()
do { do {
try proc.run() try proc.run()
// Read pipe before waitUntilExit to avoid potential deadlock
let data = pipe.fileHandleForReading.readToEndSafely()
proc.waitUntilExit() proc.waitUntilExit()
guard !data.isEmpty else { return nil }
return String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
} catch { } catch {
return nil return nil
} }
let data = pipe.fileHandleForReading.readToEndSafely()
guard !data.isEmpty else { return nil }
return String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
} }
private static func parseListeners(from text: String) -> [Listener] { private static func parseListeners(from text: String) -> [Listener] {

View File

@@ -134,6 +134,8 @@ enum RuntimeLocator {
do { do {
try process.run() try process.run()
// Read pipe before waitUntilExit to avoid potential deadlock
let data = pipe.fileHandleForReading.readToEndSafely()
process.waitUntilExit() process.waitUntilExit()
let elapsedMs = Int(Date().timeIntervalSince(start) * 1000) let elapsedMs = Int(Date().timeIntervalSince(start) * 1000)
if elapsedMs > 500 { if elapsedMs > 500 {
@@ -149,7 +151,6 @@ enum RuntimeLocator {
bin=\(binary, privacy: .public) bin=\(binary, privacy: .public)
""") """)
} }
let data = pipe.fileHandleForReading.readToEndSafely()
return String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) return String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
} catch { } catch {
let elapsedMs = Int(Date().timeIntervalSince(start) * 1000) let elapsedMs = Int(Date().timeIntervalSince(start) * 1000)