fix: audit fix — avoid latent stdout pipe deadlock in run_command (v0.8.3)
Piping stdout without draining while blocking in child.wait() risks deadlock if a command writes more than one OS pipe buffer (~64 KB on Linux). Current callers (systemctl, niri msg, loginctl) stay well under that, but the structure was fragile. stdout is now discarded; stderr continues to be captured for error reporting.
This commit is contained in:
@@ -2,6 +2,13 @@
|
||||
|
||||
Architectural and design decisions for Moonset, in reverse chronological order.
|
||||
|
||||
## 2026-04-24 – Audit fix: avoid latent stdout pipe deadlock in run_command (v0.8.3)
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
- **Why**: Audit found that `run_command` piped the child's `stdout` but never drained it, then called blocking `child.wait()`. A child writing more than one OS pipe buffer (~64 KB on Linux) would block on `write()` while the parent blocked in `wait()` — classic pipe deadlock, broken only by the 30 s SIGKILL timeout. Current callers (`systemctl`, `niri msg`, `loginctl`) do not emit that much output, but the structure was fragile and would bite on any future command or changed behaviour.
|
||||
- **Tradeoffs**: stdout is now fully discarded. If a future caller needs stdout, it will have to drain it concurrently with `wait()` (separate reader thread).
|
||||
- **How**: Replace `.stdout(Stdio::piped())` with `.stdout(Stdio::null())` in `run_command`. `stderr` stays piped — it is drained after `wait()`, which is safe because `wait()` already reaped the child and no further writes can occur.
|
||||
|
||||
## 2026-03-31 – Fourth audit: release profile, GResource compression, lock stderr, sync markers
|
||||
|
||||
- **Who**: ClaudeCode, Dom
|
||||
|
||||
Reference in New Issue
Block a user