Skip to content

Should Rust still ignore SIGPIPE by default? #62569

Open

Description

@joshtriplett

View all comments

Back in 2014, the Rust startup code (which runs before main) started ignoring SIGPIPE by default: #13158

This dates back to when Rust had a runtime and a green-threads implementation, and more magic handling of I/O. The pull request mentions that it "brings the behavior inline with libgreen".

This doesn't seem to be documented anywhere, as far as I can tell; at a minimum, this needs documentation.

But I'd like to question whether we should still do this by default. See #46016 for some recent discussion of this. This changes the default behavior of pipes from what people might expect when writing UNIX applications. Rust currently resets the signal mask in a child process, but that's only if you use Rust to launch the child process rather than calling a library to do so. And in addition to that, signal handlers are process-wide, and people might not expect Rust to change process-wide state like this. This also means that Rust libraries will not have the same behavior as Rust applications.

Either way, this is something we need to document far better. Some people will want one behavior, and some people will want the other, so whichever we use as the default needs careful documentation and a documented procedure to change it.

EDIT (based on a suggestion from @Centril): Perhaps we could control this with an attribute on main or on the top-level module?

Activity

  1. joshtriplett commented on Jul 10, 2019

    @joshtriplett
    MemberAuthor

    I'm not sure if the @rust-lang/libs team is the right team for this; suggestions welcome if another team would be the right owner for "behavior of Rust at application start-up".

  2. sfackler commented on Jul 10, 2019

    @sfackler
    Member

    If we stop ignoring SIGPIPE by default, we will break back compat for every socket server written in Rust, right?

  3. Centril commented on Jul 10, 2019

    @Centril
    Contributor

    Perhaps we should have a build-time option for this to make it easy to disable/enable in an application?

    Why this and e.g. not an attribute on fn main() {? (As a general rule, I think having program semantics defined as much as possible by the source code is preferable to flags since it makes things more standalone.)

  4. joshtriplett commented on Jul 10, 2019

    @joshtriplett
    MemberAuthor

    @sfackler Right now, we have inconsistent behavior, where your socket server will also break if you ever write main() in any language other than Rust, and turn your code into a library. That flies in the face of one of Rust's greatest strengths: "we don't have a runtime". I discovered this when looking over the pre-main startup code.

    At the very least, we need to document that we ignore SIGPIPE, and we need to provide an easy way to disable this behavior. I'd also fully expect that any change to the default itself would require a sizable transition period (or even an edition).

    @Centril Good point! An attribute within the source code (on main or the top-level module) does indeed seem preferable, rather than a build-time option.

  5. added
    A-runtimeArea: std's runtime and "pre-main" init for handling backtraces, unwinds, stack overflows
    on Jul 11, 2019
  6. BurntSushi commented on Jul 11, 2019

    @BurntSushi
    Member

    I agree that we should do something about this. I don't think we can change the current default behavior because of backcompat concerns, as @sfackler mentioned, but the current status quo is that nominal command line applications start out of the gate as broken, and it's a fairly subtle issue to fix. Firstly, if you're using println! and have enough output to exceed stdout's default buffer size, then it's likely that running, for example, command | head -n1 will result in a panic. Secondly, if you're using writeln!(...)? instead, and are setting an error code when writeln!(...) fails, then it's likely that command | head -n1 will stop with an unexpected exit code.

    For example, normally a process will exit with a 141 exit code upon receipt of a pipe error:

    $ seq 1 10000 | head -n1
    1
    $ echo ${pipestatus[1]}
    141
    

    So to fix this, you firstly need to know enough to use writeln!(...), and then you either need to assume all errors produced by writeln!(...) are broken pipe errors (and emit the correct exit code), or explicitly check errors for io::ErrorKind::BrokenPipe and respond accordingly. The former is, strictly speaking, incorrect, even though it's likely the only error you'll encounter when writing to stdout is a broken pipe error (but the code using write! may not know that it is writing to stdout). The latter works, but it's tedious, and even I get it wrong:

    $ rg . rust/ | head -n1
    rust/bstr/rustfmt.toml:disable_all_formatting = true
    $ echo ${pipestatus[1]}
    0
    

    This, presumably, should be 141. But at least it doesn't panic.

    So yeah, on the one hand, ignoring SIGPIPE by default means pure Rust networked applications probably have the right behavior by default, but not ignoring SIGPIPE by default means command line applications probably have the wrong behavior by default. Both of these groups seem fairly sizable to me.

    I don't mind the idea of some incantation one can put in the source code to fix this. An alternative might be to just provide a function in std, if it's possible, that un-ignores SIGPIPE and is intended to run at the beginning of main. If that's workable, then that might be preferrable since it adds fewer directives to the language itself. The other issue here is documentation and discoverability. i.e., How can we help folks writing CLI tools discover, in the first place, that they need to un-ignore SIGPIPE?

    (Although, I guess adding a function to std and requiring users to call it doesn't fix @joshtriplett's case, where Rust is a library. Am I understanding that right?)

  7. alexcrichton commented on Jul 11, 2019

    @alexcrichton
    Member

    I personally agree with @sfackler that I don't think we can really change any defaults due to backwards-compatibility reasons. AFAIK this really only affects one thing which is unix-y CLI applications. Any portable CLI application to Windows cannot rely on the existence of SIGPIPE and almost all non-CLI applications I imagine want to receive errors instead of SIGPIPE to handle. That feels, to me, niche enough that we made the defaults the right way and there should be documentation/crates for authors writing unix-y CLI applications (aka ripgrep and such).

  8. joshtriplett commented on Jul 15, 2019

    @joshtriplett
    MemberAuthor

    @alexcrichton I agree that we have to handle backwards-compatibility, but that wouldn't stop us from having a top-level attribute to opt-out of it.

  9. alexcrichton commented on Jul 15, 2019

    @alexcrichton
    Member

    If the proposal ends up boiling down to adding a new top-level attribute to opt-out, then I think a blocker in my mind for the proposal would be to justify why a change to the compiler/standard library is necessary when otherwise a crate on crates.io would only require two lines to add to a project (e.g. a dependency directive in Cargo.toml and a one-liner in src/main.rs to enable)

  10. joshtriplett commented on Jul 16, 2019

    @joshtriplett
    MemberAuthor

    @alexcrichton Because there's a difference between "do some setup to ignore SIGPIPE, then un-ignore it" and "never ignore SIGPIPE in the first place". Because people want to build smaller, faster executables. Because libraries and applications shouldn't have subtle differences in behavior, especially undocumented differences. Because you can do this in C and Rust makes it difficult. Because not every project should need to have a dependency to do this. Because it's a pain to add a platform-specific dependency and a platform-specific one-liner for an application that could otherwise be portable, and #![no_ignore_sigpipe] would be easier. Because it should be possible to write Rust code that doesn't have any startup code at all, and with some care we could potentially eliminate all of the current startup code in the future.

  11. alexcrichton commented on Jul 17, 2019

    @alexcrichton
    Member

    Er sorry that sort of feels like a particularly antagonistic reponse to me? Many of your "Because ..." can be pretty trivially fixed (the crate doesn't have to be platform specific, difficulty seems like it's a function of the crate, etc) and seem like they're maybe blowing the problem out of proportion (we're not slowing down executables or bloating them by adding an extra syscall at the beginning)?

    I think one of the main points you're bringing up is the difference in library/binary behavior today, but from what @sfackler mentioned above and I agree we unfortunately can't change the binary behavior and library authors largely just need to be aware of this if they run into it (which thankfully isn't all the time). We can of course also have crates to smooth this over one way or another.

    My point (which I don't think you really addressed?) was that I don't understand why a compiler solution is needed. It seems that a suitable crate can be added to crates.io to solve almost all of these issues.

  12. OvermindDL1 commented on Jul 17, 2019

    @OvermindDL1

    My point (which I don't think you really addressed?) was that I don't understand why a compiler solution is needed. It seems that a suitable crate can be added to crates.io to solve almost all of these issues.

    It's possible to make a crate that causes start to never register a sigpipe handler at all? I thought that was pretty baked in?

  13. Centril commented on Jul 17, 2019

    @Centril
    Contributor

    If this can be reasonably supported as a library function then that seems like the best solution since it is low cost. At any rate if people absolutely don't want to take on that dependency then libstd is still lower cost than a built in attribute. All in all I think @alexcrichton makes fine points.

  14. 86 remaining items

  15. added a commit that references this issue on Aug 24, 2025
  16. the8472 commented on Dec 18, 2025

    @the8472
    Member

    #62569 (comment)

    An equivalent to MSG_NOSIGNAL for write() would be much more palatable but that does not appear to exist.

    Linux 6.18 added RWF_NOSIGNAL to pwritev syscalls.

    Maybe in a decade or two when it diffuses to other OSes... 😮‍💨

  17. added a commit that references this issue on Apr 14, 2026
  18. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-runtimeArea: std's runtime and "pre-main" init for handling backtraces, unwinds, stack overflowsI-lang-radarItems that are on lang's radar and will need eventual work or consideration.T-langRelevant to the language teamT-libsRelevant to the library team, which will review and decide on the PR/issue.maybe-future-editionSomething we may consider for a future edition.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions