rust-lang / rust-lang/rust

Tracking Issue for Externally Implementable Item `#[std::io::on_broken_pipe]`

Open
#150,588 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

C-tracking-issue S-tracking-unimplemented T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description


No discussions here please. Use this Zulip topic instead. Or create discussion issues.

Feature gate: #![feature(on_broken_pipe)]

This is a tracking issue for the externally implementable item std::io::on_broken_pipe() -> std::io::OnBrokenPipe that allows programs to affect the SIGPIPE setup code that runs before fn main() is invoked.

Supersedes: https://github.com/rust-lang/rust/issues/97889 (which will be closed once we have a bare-bones implementation in place.)

Usage

A Rust program that writes a sizeable amount of data to stdout with println!() will panic if its output is piped to a short-lived program:

fn main() {
    loop {
        println!("hello world");
    }
}
% ./main | head
hello world
thread 'main' (3260965) panicked at library/std/src/io/stdio.rs:1165:9:
failed printing to stdout: Broken pipe (os error 32)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This is because SIGPIPE is changed to SIG_IGN before fn main() is invoked. To prevent panicking, a program can override the new externally implementable item to request that SIGPIPE is not changed before fn main() is invoked. Its disposition will remain SIG_DFL and the program will be killed without error when the pipe is closed:

#![feature(on_broken_pipe)]

/// The standard library asks this function how to setup `SIGPIPE` before `fn main()` is invoked.
/// Here we tell it to inherit `SIGPIPE` from the parent process, which in practice means `SIG_DFL`.
/// This implememtation can also come from an external crate that we link with.
#[std::io::on_broken_pipe]
fn inherit_on_broken_pipe() -> std::io::OnBrokenPipe {
    std::io::OnBrokenPipe::Inherit
}

fn main() {
    loop {
        println!("hello world");
    }
}
% ./main | head
hello world
Public API
/// How to change SIGPIPE disposition before `fn main()` is invoked. This lives
/// in `std::io` and is an Externally Implementable Item (eii) that can be
/// overidden by crates even though it is called by std itself.
pub fn on_broken_pipe() -> std::io::OnBrokenPipe {
    std::io::OnBrokenPipe::Default
}

/// Specifies what [`ErrorKind::BrokenPipe`] behavior a program should have. In
/// practice this affects the `SIGPIPE` setup code that runs before `fn main()`.
/// Currently only relevant to the `unix` family of operating systems.
#[non_exhaustive] // We want to be able to add more variants later.
pub enum OnBrokenPipe {
    /// Set `SIGPIPE` to `SIG_IGN` so that pipe I/O problems are reported as
    /// [`ErrorKind::BrokenPipe`] errors. Reset `SIGPIPE` to `SIG_DFL` before
    /// child `exec()`.
    ///
    /// Both of these behaviors have been the default since Rust 1.0.
    BackwardsCompatible,
    /// Set `SIGPIPE` to `SIG_IGN` so that pipe I/O problems kills the process.
    /// Don't touch `SIGPIPE` before child `exec()`.
    ///
    /// This is mainly useful when you want programs to terminate when their
    /// output is piped to short-lived programs like `head`.
    Kill,
    /// Set `SIGPIPE` to `SIG_DFL` so that pipe I/O problems are reported as
    /// [`ErrorKind::BrokenPipe`] errors. Don't touch `SIGPIPE` before child
    /// `exec()`.
    Error,
    /// Never touch `SIGPIPE`, including before child `exec()`.
    /// `SIGPIPE` disposition is always inherited from the parent process.
    /// This typically means that programs behave as with [`Self::Kill`].
    Inherit,
}
Steps

(Remember to update the S-tracking-* label when checking boxes.)

History

This feature will solve:

This feature was originally implemented as an attribute #[unix_sigpipe = "..."]. It was later changed to a compiler flag -Zon-broken-pipe=.... It is now implemented as an externally implementable item std::io::on_broken_pipe() -> std::io::OnBrokenPipe.

Unresolved Questions
  • Should we stabilize OnBrokenPipe::Kill and Error or is Inherit and Default sufficient?
  • Can we stabilize #[feature(on_broken_pipe)] without stabilizing #![feature(extern_item_impls)]?
Unresolved Questions That Does Not Block Stabilisation

Because these questions can be resolved after stabilization.

Resolved Questions
  • Can and should we alter the BrokenPipe error message and make it suggest to use the new attribute? Answer: No, because that would mean we would end up giving developer advice to users that can't act on the advice.
  • Can we use MSG_NOSIGNAL with send() etc instead of setting SIGPIPE globally? Answer: No, because there is no equivalent for write(), and it would incur an extra syscall for each write-operation, which is likely to have significant performance drawbacks.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the basic implementation checklist, PR #150591, and the SIGPIPE-related code referenced at library/std/src/io/stdio.rs. Review the old tracking issue #97889 and the linked Zulip discussion before working through the remaining implementation, documentation, and stabilization items. Done means the checklist and unresolved questions are addressed and the feature is ready for stabilization.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.