Skip to content

Commit db999dc

Browse files
committed
Reusable handle to thread-local replaceable stdout/err
Lays the groundwork for mitigating: - rust-lang#12309 - rust-lang#31983 - rust-lang#40298 - rust-lang#42474
1 parent 91db9dc commit db999dc

File tree

3 files changed

+103
-62
lines changed

3 files changed

+103
-62
lines changed

src/libstd/io/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ pub use self::stdio::{_print, _eprint};
297297
#[unstable(feature = "libstd_io_internals", issue = "42788")]
298298
#[doc(no_inline, hidden)]
299299
pub use self::stdio::{set_panic, set_print};
300+
#[unstable(feature = "set_stdio",
301+
reason = "this may disappear completely or be replaced \
302+
with a more general mechanism",
303+
issue = "0")]
304+
pub use self::stdio::{LocalStderr, LocalStdout};
300305

301306
pub mod prelude;
302307
mod buffered;

src/libstd/io/stdio.rs

Lines changed: 95 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,97 @@ thread_local! {
2626
}
2727
}
2828

29+
/// Stderr used by the default panic handler, eprint!, and eprintln! macros
30+
thread_local! {
31+
pub(crate) static LOCAL_STDERR: RefCell<Option<Box<Write + Send>>> = {
32+
RefCell::new(None)
33+
}
34+
}
35+
36+
/// Get a handle to the `local` output stream if possible,
37+
/// falling back to using `global` otherwise.
38+
///
39+
/// This function is used to print error messages, so it takes extra
40+
/// care to avoid causing a panic when `local_stream` is unusable.
41+
/// For instance, if the TLS key for the local stream is
42+
/// already destroyed, or if the local stream is locked by another
43+
/// thread, it will just fall back to the global stream.
44+
#[inline]
45+
fn with_write<W: Write, R, F: Fn(&mut io::Write) -> R>(
46+
local: &'static LocalKey<RefCell<Option<Box<Write+Send>>>>,
47+
global: fn() -> W,
48+
f: F,
49+
) -> R {
50+
local.try_with(|s| {
51+
if let Ok(mut borrowed) = s.try_borrow_mut() {
52+
if let Some(w) = borrowed.as_mut() {
53+
return f(w);
54+
}
55+
}
56+
f(&mut global())
57+
}).unwrap_or_else(|_| {
58+
f(&mut global())
59+
})
60+
}
61+
62+
/// A `Write` handle that is usually the same as using [`io::stdout()`],
63+
/// but is overridden during test runs thread-locally to capture output.
64+
/// This is how `println!` family macros work during test runs.
65+
#[unstable(feature = "set_stdio",
66+
reason = "this may disappear completely or be replaced \
67+
with a more general mechanism",
68+
issue = "0")]
69+
#[derive(Debug)]
70+
pub struct LocalStdout;
71+
72+
/// A `Write` handle that is usually the same as using [`io::stderr()`],
73+
/// but is overridden during test runs thread-locally to capture output.
74+
/// This is how `eprintln!` family macros work during test runs.
75+
#[unstable(feature = "set_stdio",
76+
reason = "this may disappear completely or be replaced \
77+
with a more general mechanism",
78+
issue = "0")]
79+
#[derive(Debug)]
80+
pub struct LocalStderr;
81+
82+
#[unstable(feature = "set_stdio",
83+
reason = "this may disappear completely or be replaced \
84+
with a more general mechanism",
85+
issue = "0")]
86+
impl Write for LocalStdout {
87+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
88+
with_write(&LOCAL_STDOUT, stdout, move |w| w.write(buf))
89+
}
90+
fn flush(&mut self) -> io::Result<()> {
91+
with_write(&LOCAL_STDOUT, stdout, move |w| w.flush())
92+
}
93+
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
94+
with_write(&LOCAL_STDOUT, stdout, move |w| w.write_all(buf))
95+
}
96+
fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
97+
with_write(&LOCAL_STDOUT, stdout, move |w| w.write_fmt(fmt))
98+
}
99+
}
100+
101+
#[unstable(feature = "set_stdio",
102+
reason = "this may disappear completely or be replaced \
103+
with a more general mechanism",
104+
issue = "0")]
105+
impl Write for LocalStderr {
106+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
107+
with_write(&LOCAL_STDERR, stderr, move |w| w.write(buf))
108+
}
109+
fn flush(&mut self) -> io::Result<()> {
110+
with_write(&LOCAL_STDERR, stderr, move |w| w.flush())
111+
}
112+
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
113+
with_write(&LOCAL_STDERR, stderr, move |w| w.write_all(buf))
114+
}
115+
fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()> {
116+
with_write(&LOCAL_STDERR, stderr, move |w| w.write_fmt(fmt))
117+
}
118+
}
119+
29120
/// A handle to a raw instance of the standard input stream of this process.
30121
///
31122
/// This handle is not synchronized or buffered in any fashion. Constructed via
@@ -625,7 +716,6 @@ impl<'a> fmt::Debug for StderrLock<'a> {
625716
issue = "0")]
626717
#[doc(hidden)]
627718
pub fn set_panic(sink: Option<Box<Write + Send>>) -> Option<Box<Write + Send>> {
628-
use panicking::LOCAL_STDERR;
629719
use mem;
630720
LOCAL_STDERR.with(move |slot| {
631721
mem::replace(&mut *slot.borrow_mut(), sink)
@@ -658,56 +748,22 @@ pub fn set_print(sink: Option<Box<Write + Send>>) -> Option<Box<Write + Send>> {
658748
})
659749
}
660750

661-
/// Write `args` to output stream `local_s` if possible, `global_s`
662-
/// otherwise. `label` identifies the stream in a panic message.
663-
///
664-
/// This function is used to print error messages, so it takes extra
665-
/// care to avoid causing a panic when `local_stream` is unusable.
666-
/// For instance, if the TLS key for the local stream is
667-
/// already destroyed, or if the local stream is locked by another
668-
/// thread, it will just fall back to the global stream.
669-
///
670-
/// However, if the actual I/O causes an error, this function does panic.
671-
fn print_to<T>(
672-
args: fmt::Arguments,
673-
local_s: &'static LocalKey<RefCell<Option<Box<Write+Send>>>>,
674-
global_s: fn() -> T,
675-
label: &str,
676-
)
677-
where
678-
T: Write,
679-
{
680-
let result = local_s.try_with(|s| {
681-
if let Ok(mut borrowed) = s.try_borrow_mut() {
682-
if let Some(w) = borrowed.as_mut() {
683-
return w.write_fmt(args);
684-
}
685-
}
686-
global_s().write_fmt(args)
687-
}).unwrap_or_else(|_| {
688-
global_s().write_fmt(args)
689-
});
690-
691-
if let Err(e) = result {
692-
panic!("failed printing to {}: {}", label, e);
693-
}
694-
}
695-
696751
#[unstable(feature = "print_internals",
697752
reason = "implementation detail which may disappear or be replaced at any time",
698753
issue = "0")]
699754
#[doc(hidden)]
700755
pub fn _print(args: fmt::Arguments) {
701-
print_to(args, &LOCAL_STDOUT, stdout, "stdout");
756+
LocalStdout.write_fmt(args)
757+
.unwrap_or_else(|e| panic!("failed printing to stdout: {}", e));
702758
}
703759

704760
#[unstable(feature = "print_internals",
705761
reason = "implementation detail which may disappear or be replaced at any time",
706762
issue = "0")]
707763
#[doc(hidden)]
708764
pub fn _eprint(args: fmt::Arguments) {
709-
use panicking::LOCAL_STDERR;
710-
print_to(args, &LOCAL_STDERR, stderr, "stderr");
765+
LocalStderr.write_fmt(args)
766+
.unwrap_or_else(|e| panic!("failed printing to stderr: {}", e));
711767
}
712768

713769
#[cfg(test)]

src/libstd/panicking.rs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,20 @@
1919
2020
use core::panic::BoxMeUp;
2121

22-
use io::prelude::*;
23-
2422
use any::Any;
25-
use cell::RefCell;
2623
use core::panic::{PanicInfo, Location};
2724
use fmt;
2825
use intrinsics;
26+
use io::LocalStderr;
2927
use mem;
3028
use ptr;
3129
use raw;
32-
use sys::stdio::{Stderr, stderr_prints_nothing};
30+
use sys::stdio::stderr_prints_nothing;
3331
use sys_common::rwlock::RWLock;
3432
use sys_common::thread_info;
3533
use sys_common::util;
3634
use thread;
3735

38-
thread_local! {
39-
pub static LOCAL_STDERR: RefCell<Option<Box<Write + Send>>> = {
40-
RefCell::new(None)
41-
}
42-
}
43-
4436
// Binary interface to the panic runtime that the standard library depends on.
4537
//
4638
// The standard library is tagged with `#![needs_panic_runtime]` (introduced in
@@ -193,7 +185,6 @@ fn default_hook(info: &PanicInfo) {
193185
None => "Box<Any>",
194186
}
195187
};
196-
let mut err = Stderr::new().ok();
197188
let thread = thread_info::current_thread();
198189
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
199190

@@ -215,18 +206,7 @@ fn default_hook(info: &PanicInfo) {
215206
}
216207
};
217208

218-
let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take());
219-
match (prev, err.as_mut()) {
220-
(Some(mut stderr), _) => {
221-
write(&mut *stderr);
222-
let mut s = Some(stderr);
223-
LOCAL_STDERR.with(|slot| {
224-
*slot.borrow_mut() = s.take();
225-
});
226-
}
227-
(None, Some(ref mut err)) => { write(err) }
228-
_ => {}
229-
}
209+
write(&mut LocalStderr);
230210
}
231211

232212

0 commit comments

Comments
 (0)