# Defusing println! when stdout is closed

**URL:** <https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325>\
**Category:** libs\
**Created:** [February 14, 2024, 5:55pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325 "2024-02-14T17:55:08Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [February 14, 2024, 5:55pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/1 "2024-02-14T17:55:08Z")

</div>

`println!` will panic when stdout is closed. There's proposed `#[unix_sigpipe = "sig_dfl"]` to address this, but it elevates panics to killing the process, which for my use-cases only makes things worse. I'm missing a third option that ignores the error (`sig_ign` ignores the signal, but not the write error).

`println!` panic is a recurring annoyance for me in **server** development, because it tends to happen during server shutdown and causes noise in crash reports. My servers need to support zero-downtime restarts, so they're _not_ supposed to be suddenly killed on restart whenever that can be avoided. They also have error reporting like Sentry that makes a big fuss about every panic (usually for a good reason). But there are conditions where a supervisor of the server process closes it stdout/stderr before the server quits, and then any print during shutdown can cause a panic. Some server components can become chatty during shutdown when things they depend on become unavailable, and use of `println!` is quite common, and replacing with something like `let _ = write!(stdout(), "")` is a whack-a-mole.

So I'm wondering, could there be a way to defuse `println!`?

Maybe as a different flavor of `#[unix_sigpipe = "sig_dfl"]` that means ignoring both the signal and the error in `(e)println!`, or maybe some libstd function, that I could call at the start of shutdown sequence, that uses its stdout capture mechanism to redirect println! to `/dev/null` or give it an error-swallowing wrapper on stdout.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [February 14, 2024, 6:38pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/2 "2024-02-14T18:38:28Z")

</div>

I've thought for a while that closing `std::io::std{in,out,err}` should actually replace them with a handle to /dev/null, at the level of OS file descriptors. Thus, your "libstd function, that I could call at the start of shutdown sequence" would be `io::stdout().close()`.

---

<div class="post-metadata">

**Author:** ![chrisd](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/chrisd/32/7232_2.png) [@chrisd](https://internals.rust-lang.org/u/chrisd)\
**Post date:** [February 14, 2024, 7:08pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/3 "2024-02-14T19:08:46Z")

</div>

rustc has needed this so much it has it's own [`safe_print`](https://github.com/rust-lang/rust/blob/master/compiler/rustc_driver_impl/src/print.rs) macro that avoids ICEing when there's an error (it still raises a fatal error but that's different to bare panicking).

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [February 14, 2024, 8:36pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/4 "2024-02-14T20:36:30Z")

</div>

Isn't best practice for servers to use the tracing or log ecosystems of crates rather than println? Maybe that is a better way to deal with the problem for your use case. Then your logging/tracing handling crate just needs to do its printing correctly.

---

<div class="post-metadata">

**Author:** ![the8472](https://avatars.discourse-cdn.com/v4/letter/t/0ea827/32.png) [@the8472](https://internals.rust-lang.org/u/the8472)\
**Post date:** [February 14, 2024, 11:00pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/5 "2024-02-14T23:00:03Z")

</div>

I'm assuming stdout is a pipe which causes the sigpipe or epipe. Replace it with `/dev/null`, which won't error.

Yet another problem that could be solved by a `stdout::take()`.

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [February 15, 2024, 1:46am UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/6 "2024-02-15T01:46:34Z")

</div>

myrrlyn wrote the [calm\_io](https://crates.io/crates/calm_io) crate a while back which provides `stdoutln!` as an alternative to `println!` which returns the `io::Result` instead of unwrapping it.

I believe clippy and/or cargo-deny are capable of linting on `println!` usage to assist in playing whack-a-mole.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [February 15, 2024, 1:45pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/7 "2024-02-15T13:45:13Z")

</div>

> [@CAD97](#):
>
> `stdoutln!` as an alternative to `println!` which returns the `io::Result` instead of unwrapping it.

Hmm. How often does one want this but _not_ want to wrap stdout in a `BufWriter`? I have been working on a CLI program lately that does a lot of output to stdout. Its `main` looks something like

```rust
fn main() -> ExitCode {
    let args = parse_cmdline(); // doesn't return on error or --help
    let mut stdout = io::stdout().lock();
    // ... wrap stdout in a BufWriter if it's not a tty ...
    if let Err(e) = run(stdout, args) {
        eprintln!("{}", e);
        return ExitCode::FAILURE;
    }
    ExitCode::SUCCESS
}

```

and then everything that wants to print to stdout uses `writeln!(stdout, ...)?` and it works out nicely.

For stderr it makes more sense, up to a point: if writing to stderr doesn't work, where do you report that error?? In the above snippet I'd be happy for the eprintln! to ignore errors, since the program is about to exit unsuccessfully anyway. In a program like rustc, where stderr might get lots of output in mid-execution, I think I'd go with passing down a handle and using writeln! and discarding failures.

---

<div class="post-metadata">

**Author:** ![Enselic](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/enselic/32/9262_2.png) [@Enselic](https://internals.rust-lang.org/u/Enselic)\
**Post date:** [February 16, 2024, 5:14am UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/8 "2024-02-16T05:14:15Z")

</div>

> There's proposed `#[unix_sigpipe = "sig_dfl"]` to address this, but it elevates panics to killing the process

I would like to clarify how `#[unix_sigpipe = "sig_dfl"]` works and what it does, with the hope that it will be helpful. I would not say that it "elevates panics to killing the process".

A `write` syscall to the fd of a closed pipe will trigger the `SIGPIPE` signal. For crate type bin, Rust by default sets the `SIGPIPE` handler to `SIG_IGN` before `fn main()` is invoked. This causes `SIGPIPE` to be ignored, and the `write` syscall will return `EPIPE`, which will be converted to a `std::io::ErrorKind::BrokenPipe`. The `println()` implementation will `unwrap()` this error, causing a panic.

With `#[unix_sigpipe = "sig_dfl"]`, Rust will set the `SIGPIPE` handler to `SIG_DFL` before `fn main()` is invoked. Now when a `write` syscall to the fd of a closed pipe triggers `SIGPIPE`, the process will be killed before `write` returns (which is the default behavior of `SIGPIPE`). A panic will never occur, because the process is killed before there is any chance to trigger a panic. Note in particular that this means that destructors will not be run (which explicitly is not UB in Rust).

> So I'm wondering, could there be a way to defuse println!?

Maybe, but I don't think `#[unix_sigpipe = "..."]` will be useful for it.

---

<div class="post-metadata">

**Author:** ![Noratrieb](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/noratrieb/32/12440_2.png) [@Noratrieb](https://internals.rust-lang.org/u/Noratrieb)\
**Post date:** [February 21, 2024, 8:53pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/9 "2024-02-21T20:53:42Z")

</div>

In my opinion, `println!` is not intended for production systems or tools, but for use cases that are closer to hello world or debugging that don't care about absolute correctness. SIGPIPE is a complex question with many possible behaviors, and I think that for more "serious" applications that care about handling it correctly, Rusts SIG\_IGN is the correct solution because it allows the application to handle it however they want in different places. Dependong on what they're printing and the importance of that print, behavior might be different. A CLI that prints output without doing any side effects will probably want to exit, so it can define a wrapper around `write!(stdout())` that handles `ErrorKind::BrokenPipe` and exits (or it could use the `unix_sigpipe` attribute, but I don't particularly like that attribute, I think writing a custom wrapper is nicer as it makes it more explicit when we may exit).

For server logs, just ignoring the error makes a lot of sense. If a a logging library like `tracing` or `log` is used, this can be centralized in one place.

I don't think we should just ignore errors by default. If the user wanted to write something somewhere, but writing it failed, that's an error condition in general. Whether it actually is an error that should be displayed, or whether it should just be ignored should be up to the programmer.

As for whack-a-mole-ing out `println!`, Clippy has lints for this that can be used to reliably avoid `println!` (which in my experience isn't a big problem for server software when a nice logging framework like `tracing` is used, which is really nice to use, but I haven't written that much server code in Rust): [Clippy Lints](https://rust-lang.github.io/rust-clippy/master/#/print_stdout)

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [February 21, 2024, 9:51pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/10 "2024-02-21T21:51:16Z")

</div>

> [@Noratrieb](#):
>
> define a wrapper around `write!(stdout())` that handles `ErrorKind::BrokenPipe` and exits (or it could use the `unix_sigpipe` attribute, but I don't particularly like that attribute, I think writing a custom wrapper is nicer as it makes it more explicit when we may exit).

Just to note, there is a meaningful difference here. On SIGPIPE, a unix process could:

- Continue on screaming into the `/dev/null` void.
- Exit gracefully, claiming success with `exit(0)`.
- Exit gracefully, claiming error with `exit(1..=255)`.
- Exit nongracefully, attempting to complain to stderr (e.g. `panic!`).
- Exit abruptly with SIGPIPE.

That last one IIUC cannot be replicated without the `#[unix_sigpipe]` attribute, and looks different to the parent process, as it causes the process to report an exit code of `256 + SIGPIPE`, which is not possible except for process killed by SIGPIPE. This difference can have an impact on how shells respond to process termination.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [February 22, 2024, 1:28pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/11 "2024-02-22T13:28:48Z")

</div>

Just responding to this specifically:

> [@CAD97](#):
>
> [Exiting abruptly with SIGPIPE] IIUC cannot be replicated without the `#[unix_sigpipe]` attribute

There is a way, but it's a bit of a pain: at the point where you want to turn the EPIPE error back into a fatal SIGPIPE, join all other threads that are still running, unblock SIGPIPE, reset its disposition to SIG\_DFL, and then `raise(SIGPIPE)`. I can't think of any program that actually bothers for SIGPIPE, but I _have_ seen guides that encourage you to do the equivalent at the end of clean-up-before-exiting SIGINT (^C) handlers, so the technique is out there in people's minds.

EDIT: It occurred to me immediately after posting that this could _almost_ be done generically in `std`-for-Unix by adding a `from_signal` constructor to `ExitCode`. (It would have to be fallible, accepting only those signal numbers whose default action is to kill the process. There is no way to make a signal whose default action is "do nothing", like SIGCHLD or SIGWINCH, kill the process.)

Returning that variant of ExitCode from main, or calling `exit_process` on it, would carry out the above sequence ... except there's no good way to do the "join all other threads" part, and without that you have no guarantee that you're not racing with some other thread that wants to _block_ the signal in question or change its disposition.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [February 22, 2024, 1:36pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/12 "2024-02-22T13:36:52Z")

</div>

Interesting, but hardly practical, specifically about joining all other threads: Consider for example a thread pool with rayon, I don't think there is even a way to stop the default thread pool in that case (this is relevant to a project I'm currently writing the early parts of, though the use of rayon parallel iteration is set in stone at this point).

Why do you need to join other threads for the signal handling changes in this case though?

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [February 22, 2024, 1:41pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/13 "2024-02-22T13:41:59Z")

</div>

> Why do you need to join other threads for the signal handling changes in this case though?

The signal handling settings are process-global. Another thread could revert the changes to the mask and/or disposition for SIGPIPE, after the first thread's changes but before the raise(), causing the raise() to be ineffective.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [February 22, 2024, 6:24pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/14 "2024-02-22T18:24:00Z")

</div>

Right, makes sense. And something std or a library has to worry about (but can't do much about).

But I don't know of many (any?) rust library that messes with signals internally (unless that is their purpose, eg. signal/sigaction in libc, or other crates dealing specifically with signal handling). Main reason of course being that signals are seldom the best way to deal with anything if you have another option. So for a typical application developer it is likely not a real problem.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [May 22, 2024, 6:24pm UTC](https://internals.rust-lang.org/t/defusing-println-when-stdout-is-closed/20325/15 "2024-05-22T18:24:49Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
