-
Notifications
You must be signed in to change notification settings - Fork 10
Description
This has been an issue in some monorepo user commands but also in our new doublezero-solana commands in the offchain repo. Rust's runtime ignores SIGPIPE at startup, which causes println! to panic instead of the process dying silently.
Example:
$ doublezero-solana shreds price | head -n 5
95 device(s) found:
...
thread 'main' panicked at library/std/src/io/stdio.rs:1165:9:
failed printing to stdout: Broken pipe (os error 32)
Below are three ways to restore sane broken-pipe behavior. Want to gather feedback because this requires changes in how we log stuff (println! → writeln! + error handling) or in the main files of a few components that use the CLI.
Option 1 — Reset SIGPIPE to default (libc::signal)
Rust's runtime sets SIGPIPE to SIG_IGN before fn main(). This restores the default Unix behavior by calling libc::signal at the start of main, making the process behave like a C program, it gets killed silently when a pipe reader closes. This is the standard approach used by tools like ripgrep, bat, and fd.
fn reset_sigpipe() {
#[cfg(unix)]
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
}Requires
unsafeandlibcas a direct dependency (which we already have). Fixes allprintln!calls globally.
Option 2 — Custom panic hook
Since println! panics on write errors rather than returning a Result, we can intercept the panic before it prints the error. This replaces the default panic handler with one that detects broken pipe panics and exits the process cleanly with code 0. The hook is set at the start of main.
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let is_broken_pipe = info
.payload()
.downcast_ref::<String>()
.map(|s| s.contains("Broken pipe"))
.unwrap_or(false);
if is_broken_pipe {
std::process::exit(0);
}
default_hook(info);
}));No
unsafe, no new dependencies, fixes allprintln!calls globally. Relies on string-matching the panic message, which could break if Rust changes the wording.
Option 3 — Replace println! with writeln! + catch in main
Instead of using println! which panics on errors, use writeln! which returns a Result that can be propagated with ?. The broken pipe error bubbles up to main, where we catch it and exit silently instead of printing an error trace.
// In commands:
let mut stdout = std::io::stdout().lock();
writeln!(stdout, "{table}")?;
// In main:
Err(err)
if err
.downcast_ref::<std::io::Error>()
.is_some_and(|e| e.kind() == std::io::ErrorKind::BrokenPipe) =>
{
Ok(())
}No
unsafe, no new dependencies, explicit error handling. Requires changing everyprintln!towriteln!in affected commands — only protects the commands you change.
Affected components
- offchain repo —
doublezero-solanabinary (crates/solana-cli/src/main.rs) - monorepo —
doublezero_clilibrary consumed byclient/doublezero,activator,controlplane/doublezero-admin,client/doublezero-geolocation-cli
For the monorepo, options 1 and 2 would need to be added to each binary's main.rs. Option 3 could be handled inside the library itself (so all binaries get the fix for free).