From 858865478358c627f0485f3aef17ffb8bcfee612 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 31 Oct 2015 09:41:21 -0700 Subject: [PATCH] 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 #29488 --- src/libstd/io/stdio.rs | 34 ++++++++++++++++++++++++-------- src/test/run-pass/issue-29488.rs | 30 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/test/run-pass/issue-29488.rs diff --git a/src/libstd/io/stdio.rs b/src/libstd/io/stdio.rs index 31b881bebf05f..d6a9778ced291 100644 --- a/src/libstd/io/stdio.rs +++ b/src/libstd/io/stdio.rs @@ -16,11 +16,12 @@ use cmp; 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) -> Option> { 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 index 0000000000000..eee0f663df24c --- /dev/null +++ b/src/test/run-pass/issue-29488.rs @@ -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 or the MIT license +// , 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(); +}