Fix abort-on-eprintln during process shutdown

This commit fixes an issue where if `eprintln!` is used in a TLS
destructor it can accidentally cause the process to abort. TLS
destructors are executed after `main` returns on the main thread, and at
this point we've also deinitialized global `Lazy` values like those
which store the `Stderr` and `Stdout` internals. This means that despite
handling TLS not being accessible in `eprintln!`, we will fail due to
not being able to call `stderr()`. This means that we'll double-panic
quickly because panicking also attempt to write to stderr.

The fix here is to reimplement the global stderr handle to avoid the
need for destruction. This avoids the need for `Lazy` as well as the
hidden panic inside of the `stderr` function.

Overall this should improve the robustness of printing errors and/or
panics in weird situations, since the `stderr` accessor should be
infallible in more situations.
This commit is contained in:
Alex Crichton 2020-03-12 11:39:30 -07:00
parent 23de8275c9
commit 5edaa7eefd
11 changed files with 148 additions and 99 deletions

View file

@ -0,0 +1,48 @@
// run-pass
// ignore-emscripten no processes
use std::cell::RefCell;
use std::env;
use std::process::Command;
fn main() {
let name = "YOU_ARE_THE_TEST";
if env::var(name).is_ok() {
std::thread::spawn(|| {
TLS.with(|f| f.borrow().ensure());
})
.join()
.unwrap();
} else {
let me = env::current_exe().unwrap();
let output = Command::new(&me).env(name, "1").output().unwrap();
println!("{:?}", output);
assert!(output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("hello new\n"));
assert!(stderr.contains("hello drop\n"));
}
}
struct Stuff {
_x: usize,
}
impl Stuff {
fn new() -> Self {
eprintln!("hello new");
Self { _x: 0 }
}
fn ensure(&self) {}
}
impl Drop for Stuff {
fn drop(&mut self) {
eprintln!("hello drop");
}
}
thread_local! {
static TLS: RefCell<Stuff> = RefCell::new(Stuff::new());
}