From 8cb51b5b9850dae4fe11f5b80cd9460d0c56d8c1 Mon Sep 17 00:00:00 2001 From: dsward2 Date: Mon, 29 Jun 2026 06:04:02 -0500 Subject: [PATCH] Add --exit-with-parent watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an opt-in CLI flag that makes the server exit when its parent process dies — even on a crash/SIGKILL where the parent can't run its own cleanup. A host app that launches LiveAudioServer as a helper can pass --exit-with-parent to guarantee the server is reaped rather than orphaned holding its HTTP port. Implementation: ServerConfig.exitWithParent (parsed in CLIParse) gates a DispatchSource timer in the CLI that polls getppid(); when the process is reparented (to launchd, pid 1) it runs the existing graceful shutdown. Library behavior is unchanged when the flag is absent. Co-Authored-By: Claude Opus 4.8 --- .../LiveAudioServer/LiveAudioServerApp.swift | 23 +++++++++++++++++++ Sources/LiveAudioServerCore/CLIParse.swift | 2 ++ Sources/LiveAudioServerCore/Config.swift | 5 ++++ 3 files changed, 30 insertions(+) diff --git a/Sources/LiveAudioServer/LiveAudioServerApp.swift b/Sources/LiveAudioServer/LiveAudioServerApp.swift index 2e930d6..44a555a 100644 --- a/Sources/LiveAudioServer/LiveAudioServerApp.swift +++ b/Sources/LiveAudioServer/LiveAudioServerApp.swift @@ -94,6 +94,10 @@ func printUsage() { stay live. --no-fifo-reopen Disable the FIFO re-open behaviour above; the reader will silence-fill after EOF instead. + --exit-with-parent Exit if the parent process dies (even on a + crash). Useful when launched as a helper by a + host app that needs this server reaped instead + of orphaned holding its HTTP port. --silence-dither Inject inaudible TPDF dither (±1 LSB) once a run of digitally-silent samples crosses --silence-dither-ms. Prevents downstream @@ -219,6 +223,25 @@ struct LiveAudioServerApp { sigTerm.resume() _ = sigInt; _ = sigTerm + // Parent-death watchdog (--exit-with-parent). Polls getppid(); when the + // launching app dies (quit or crash) this process is reparented to + // launchd (pid 1), so getppid() changes — at which point we shut down + // gracefully, freeing the HTTP port instead of orphaning it. + var parentWatchdog: DispatchSourceTimer? + if config.exitWithParent { + let originalParent = getppid() + let timer = DispatchSource.makeTimerSource(queue: .global()) + timer.schedule(deadline: .now() + 0.5, repeating: 0.5) + timer.setEventHandler { + if getppid() != originalParent { + runGracefulShutdown("Parent process exit") + } + } + timer.resume() + parentWatchdog = timer + } + _ = parentWatchdog + RunLoop.main.run() } } diff --git a/Sources/LiveAudioServerCore/CLIParse.swift b/Sources/LiveAudioServerCore/CLIParse.swift index 0cf58c3..7670406 100644 --- a/Sources/LiveAudioServerCore/CLIParse.swift +++ b/Sources/LiveAudioServerCore/CLIParse.swift @@ -226,6 +226,8 @@ public func parseCLI(_ args: [String]) -> CLIParseResult { config.httpAuthRealm = r case "--keep-alive": config.keepAliveOnInputEnd = true + case "--exit-with-parent": + config.exitWithParent = true case "--no-fifo-reopen": config.reopenStdinFIFO = false case "--silence-dither": diff --git a/Sources/LiveAudioServerCore/Config.swift b/Sources/LiveAudioServerCore/Config.swift index 06685bb..47053a0 100644 --- a/Sources/LiveAudioServerCore/Config.swift +++ b/Sources/LiveAudioServerCore/Config.swift @@ -62,6 +62,11 @@ public struct ServerConfig { public var aacBitrate: Int = 128_000 // bps (AudioToolbox uses bps) public var verbose: Bool = false public var stdinChunkFrames: Int = 4096 // PCM frames read per stdin iteration + /// When set, the CLI exits if its parent process dies (even on a crash where + /// the parent can't run cleanup). Lets a host app guarantee this helper is + /// reaped rather than orphaned holding its HTTP port. CLI-only; see + /// `LiveAudioServerApp`. + public var exitWithParent: Bool = false public var keepAliveOnInputEnd: Bool = false /// When the input stream is a FIFO/named pipe and `keepAliveOnInputEnd` is /// on, re-`open()` the same path on EOF so a new producer can attach.