]> git.lizzy.rs Git - rust.git/commitdiff
std: Prevent print panics when using TLS
authorAlex Crichton <alex@alexcrichton.com>
Sat, 31 Oct 2015 16:41:21 +0000 (09:41 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Sat, 31 Oct 2015 16:41:21 +0000 (09:41 -0700)
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 #29488

src/libstd/io/stdio.rs
src/test/run-pass/issue-29488.rs [new file with mode: 0644]

index 31b881bebf05f9c0672821237be88af69e7bd19f..d6a9778ced291916882fecf3634868a2bb3f320a 100644 (file)
 use fmt;
 use io::lazy::Lazy;
 use io::{self, BufReader, LineWriter};
+use libc;
 use sync::{Arc, Mutex, MutexGuard};
 use sys::stdio;
 use sys_common::io::{read_to_end_uninitialized};
 use sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
-use libc;
+use thread::LocalKeyState;
 
 /// Stdout used by print! and println! macros
 thread_local! {
@@ -576,14 +577,31 @@ pub fn set_print(sink: Box<Write + Send>) -> Option<Box<Write + Send>> {
            issue = "0")]
 #[doc(hidden)]
 pub fn _print(args: fmt::Arguments) {
-    let result = LOCAL_STDOUT.with(|s| {
-        if s.borrow_state() == BorrowState::Unused {
-            if let Some(w) = s.borrow_mut().as_mut() {
-                return w.write_fmt(args);
-            }
+    // As an implementation of the `println!` macro, we want to try our best to
+    // not panic wherever possible and get the output somewhere. There are
+    // currently two possible vectors for panics we take care of here:
+    //
+    // 1. If the TLS key for the local stdout has been destroyed, accessing it
+    //    would cause a panic. Note that we just lump in the uninitialized case
+    //    here for convenience, we're not trying to avoid a panic.
+    // 2. If the local stdout is currently in use (e.g. we're in the middle of
+    //    already printing) then accessing again would cause a panic.
+    //
+    // If, however, the actual I/O causes an error, we do indeed panic.
+    let result = match LOCAL_STDOUT.state() {
+        LocalKeyState::Uninitialized |
+        LocalKeyState::Destroyed => stdout().write_fmt(args),
+        LocalKeyState::Valid => {
+            LOCAL_STDOUT.with(|s| {
+                if s.borrow_state() == BorrowState::Unused {
+                    if let Some(w) = s.borrow_mut().as_mut() {
+                        return w.write_fmt(args);
+                    }
+                }
+                stdout().write_fmt(args)
+            })
         }
-        stdout().write_fmt(args)
-    });
+    };
     if let Err(e) = result {
         panic!("failed printing to stdout: {}", e);
     }
diff --git a/src/test/run-pass/issue-29488.rs b/src/test/run-pass/issue-29488.rs
new file mode 100644 (file)
index 0000000..eee0f66
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::thread;
+
+struct Foo;
+
+impl Drop for Foo {
+    fn drop(&mut self) {
+        println!("test2");
+    }
+}
+
+thread_local!(static FOO: Foo = Foo);
+
+fn main() {
+    // Off the main thread due to #28129, be sure to initialize FOO first before
+    // calling `println!`
+    thread::spawn(|| {
+        FOO.with(|_| {});
+        println!("test1");
+    }).join().unwrap();
+}