Skip to content

Commit 8588654

Browse files
committed
std: Prevent print panics when using TLS
Currently if a print happens while a thread is being torn down it may cause a panic if the LOCAL_STDOUT TLS slot has been destroyed by that point. This adds a guard to check and prints to the process stdout if that's the case (as we do for if the slot is already borrowed). Closes rust-lang#29488
1 parent 1a2eaff commit 8588654

File tree

2 files changed

+56
-8
lines changed

2 files changed

+56
-8
lines changed

src/libstd/io/stdio.rs

+26-8
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ use cmp;
1616
use fmt;
1717
use io::lazy::Lazy;
1818
use io::{self, BufReader, LineWriter};
19+
use libc;
1920
use sync::{Arc, Mutex, MutexGuard};
2021
use sys::stdio;
2122
use sys_common::io::{read_to_end_uninitialized};
2223
use sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
23-
use libc;
24+
use thread::LocalKeyState;
2425

2526
/// Stdout used by print! and println! macros
2627
thread_local! {
@@ -576,14 +577,31 @@ pub fn set_print(sink: Box<Write + Send>) -> Option<Box<Write + Send>> {
576577
issue = "0")]
577578
#[doc(hidden)]
578579
pub fn _print(args: fmt::Arguments) {
579-
let result = LOCAL_STDOUT.with(|s| {
580-
if s.borrow_state() == BorrowState::Unused {
581-
if let Some(w) = s.borrow_mut().as_mut() {
582-
return w.write_fmt(args);
583-
}
580+
// As an implementation of the `println!` macro, we want to try our best to
581+
// not panic wherever possible and get the output somewhere. There are
582+
// currently two possible vectors for panics we take care of here:
583+
//
584+
// 1. If the TLS key for the local stdout has been destroyed, accessing it
585+
// would cause a panic. Note that we just lump in the uninitialized case
586+
// here for convenience, we're not trying to avoid a panic.
587+
// 2. If the local stdout is currently in use (e.g. we're in the middle of
588+
// already printing) then accessing again would cause a panic.
589+
//
590+
// If, however, the actual I/O causes an error, we do indeed panic.
591+
let result = match LOCAL_STDOUT.state() {
592+
LocalKeyState::Uninitialized |
593+
LocalKeyState::Destroyed => stdout().write_fmt(args),
594+
LocalKeyState::Valid => {
595+
LOCAL_STDOUT.with(|s| {
596+
if s.borrow_state() == BorrowState::Unused {
597+
if let Some(w) = s.borrow_mut().as_mut() {
598+
return w.write_fmt(args);
599+
}
600+
}
601+
stdout().write_fmt(args)
602+
})
584603
}
585-
stdout().write_fmt(args)
586-
});
604+
};
587605
if let Err(e) = result {
588606
panic!("failed printing to stdout: {}", e);
589607
}

src/test/run-pass/issue-29488.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use std::thread;
12+
13+
struct Foo;
14+
15+
impl Drop for Foo {
16+
fn drop(&mut self) {
17+
println!("test2");
18+
}
19+
}
20+
21+
thread_local!(static FOO: Foo = Foo);
22+
23+
fn main() {
24+
// Off the main thread due to #28129, be sure to initialize FOO first before
25+
// calling `println!`
26+
thread::spawn(|| {
27+
FOO.with(|_| {});
28+
println!("test1");
29+
}).join().unwrap();
30+
}

0 commit comments

Comments
 (0)