Skip to main content

Crate pipecheck

Crate pipecheck 

Source
Expand description

Cross-platform Unix-style handling of broken pipe errors.

When any call to its underlying writer returns a BrokenPipe error, a Writer terminates the current process with a SIGPIPE signal, or falls back to a plain exit with code 1.

§Caveats

On Unix, Writer works by resetting the process-wide SIGPIPE handler to its default behavior immediately before sending SIGPIPE to the current thread. It may fall back to a plain exit:

  • If it fails to reset the default SIGPIPE handler, which should never happen on a well-behaved system.
  • If the current thread’s signal mask blocks delivery of SIGPIPE, which may happen if the current thread or its creator previously manipulated the signal mask. This version of Writer never unblocks SIGPIPE on its own (though a future version might).
  • If a racing thread installs a non-default SIGPIPE handler, in which case Writer may invoke that handler before exiting. This is not considered unsound in terms of Rust’s safety guarantees, as POSIX.1 prohibits the involved C library functions from being prone to data races or similar undefined behavior.

Non-Unix platforms always fall back to a plain exit.

§Why is this useful?

Within a shell pipeline, it’s good form for a process to exit quickly and silently as soon as its downstream stops accepting input. Unix simplifies this with the SIGPIPE signal: when a process writes to a pipe where all file descriptors referring to the read end have been closed, the system delivers it a SIGPIPE, which by default terminates it.

The existence of SIGPIPE introduces two challenges:

  1. It’s Unix-specific, so portable CLIs can’t rely on it completely.
  2. A networked server can generate SIGPIPE on writes to a socket whose client has closed its read end, and terminating the server would break other clients’ connections.

Given these challenges, the Rust developers chose to override Unix’s default behavior by ignoring SIGPIPE before calling main, so that writes to broken pipes return a plain BrokenPipe error on all platforms.

However, real-world Rust libraries don’t always expose enough detail to easily distinguish broken pipes from other errors. For example, the source implementation for a custom error might not expose an underlying io::Error even when traversing the entire chain of sources, which is problematic when error values are coalesced into a Box<dyn Error> (or similar) and passed up the call stack.

Writer instead plumbs its logic into every write, catching broken pipe errors and terminating the process before they can be lost or obscured. Compared to modifying the process-wide SIGPIPE behavior at the start of a Rust program, this approach is more cross-platform and better scoped to the specific writes where termination is desired (generally standard output and error streams).

Note that termination on Unix attempts to use the real default behavior of SIGPIPE; Writer does not employ incorrect hacks like exiting with code 141 (mimicking the shell return code of a process terminated by SIGPIPE).

§Can I avoid adding such a small crate to my supply chain?

src/pipecheck.rs contains the entire implementation of the crate with independent documentation and licensing information, with the explicit goal of easy copy-paste vendoring into your own codebase.

You will need to depend on the libc crate (for at least cfg(unix)), add mod pipecheck; to your crate root or an appropriate parent module, and ensure that your lint settings allow the module’s unsafe code.

§Further Reading

For further background on SIGPIPE, Rust’s handling of it, and cross-platform portability concerns surrounding broken pipes, see:

The concept of pipecheck was inspired by Go’s default behavior for broken pipes: terminating the program if the write was to a standard output or error stream, and otherwise returning a plain error. For background on Go’s behavior and runtime implementation, see:

Structs§

Writer
A writer that silently terminates the program on broken pipe errors.

Functions§

wrap
A convenient alias for Writer::new.