Repository navigation
Should Rust still ignore SIGPIPE by default? #62569
Description
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?
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIdea
Activity
joshtriplett commentedon Jul 10, 2019
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".
sfackler commentedon Jul 10, 2019
If we stop ignoring SIGPIPE by default, we will break back compat for every socket server written in Rust, right?
Centril commentedon Jul 10, 2019
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.)joshtriplett commentedon Jul 10, 2019
@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.
BurntSushi commentedon Jul 11, 2019
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 -n1will result in a panic. Secondly, if you're usingwriteln!(...)?instead, and are setting an error code whenwriteln!(...)fails, then it's likely thatcommand | head -n1will stop with an unexpected exit code.For example, normally a process will exit with a
141exit code upon receipt of a pipe error:So to fix this, you firstly need to know enough to use
writeln!(...), and then you either need to assume all errors produced bywriteln!(...)are broken pipe errors (and emit the correct exit code), or explicitly check errors forio::ErrorKind::BrokenPipeand 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 usingwrite!may not know that it is writing to stdout). The latter works, but it's tedious, and even I get it wrong: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 ofmain. 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
stdand requiring users to call it doesn't fix @joshtriplett's case, where Rust is a library. Am I understanding that right?)alexcrichton commentedon Jul 11, 2019
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).
joshtriplett commentedon Jul 15, 2019
@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.
alexcrichton commentedon Jul 15, 2019
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.tomland a one-liner insrc/main.rsto enable)joshtriplett commentedon Jul 16, 2019
@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.alexcrichton commentedon Jul 17, 2019
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.
OvermindDL1 commentedon Jul 17, 2019
It's possible to make a crate that causes
startto never register a sigpipe handler at all? I thought that was pretty baked in?Centril commentedon Jul 17, 2019
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.
86 remaining items
: stdio redirection (meta-pytorch#900)
: stdio redirection (meta-pytorch#900)
: stdio redirection (meta-pytorch#900)
stdio redirection (#900)
the8472 commentedon Dec 18, 2025
#62569 (comment)
Linux 6.18 added RWF_NOSIGNAL to pwritev syscalls.
Maybe in a decade or two when it diffuses to other OSes... 😮💨
Auto merge of #97802 - Enselic:add-no_ignore_sigkill-feature, r=josht…
Auto merge of #97802 - Enselic:add-no_ignore_sigkill-feature, r=josht…
Auto merge of #97802 - Enselic:add-no_ignore_sigkill-feature, r=josht…
cli/test: Add broken pipe (SIGPIPE) tests
term: Handle SIGPIPE
term: Handle SIGPIPE
cli/test: Add broken pipe (SIGPIPE) tests
term: Catch EPIPE and swallow