Skip to content

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

@Enselic

Description

@Enselic

No discussions here please. Use this Zulip topic instead.


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: #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)]
#![feature(extern_item_impls)]

/// 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.

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCS-tracking-unimplementedStatus: The feature has not been implemented.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions