summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2020-09-27 04:50:46 +0000
committerbors <bors@rust-lang.org>2020-09-27 04:50:46 +0000
commitc9e5e6a53aef5cd1b939ecfa18f56bdf5bf0451c (patch)
tree9c307037bc3918d17d1ab0093e3289534620963d
parentAuto merge of #76986 - jonas-schievink:ret-in-reg, r=nagisa (diff)
parentDisable stdout-during-shutdown test on emscripten. (diff)
downloadrust-c9e5e6a53aef5cd1b939ecfa18f56bdf5bf0451c.tar.gz
rust-c9e5e6a53aef5cd1b939ecfa18f56bdf5bf0451c.tar.bz2
rust-c9e5e6a53aef5cd1b939ecfa18f56bdf5bf0451c.tar.xz
Auto merge of #77154 - fusion-engineering-forks:lazy-stdio, r=dtolnay
Remove std::io::lazy::Lazy in favour of SyncOnceCell The (internal) std::io::lazy::Lazy was used to lazily initialize the stdout and stdin buffers (and mutexes). It uses atexit() to register a destructor to flush the streams on exit, and mark the streams as 'closed'. Using the stream afterwards would result in a panic. Stdout uses a LineWriter which contains a BufWriter that will flush the buffer on drop. This one is important to be executed during shutdown, to make sure no buffered output is lost. It also forbids access to stdout afterwards, since the buffer is already flushed and gone. Stdin uses a BufReader, which does not implement Drop. It simply forgets any previously read data that was not read from the buffer yet. This means that in the case of stdin, the atexit() function's only effect is making stdin inaccessible to the program, such that later accesses result in a panic. This is uncessary, as it'd have been safe to access stdin during shutdown of the program. --- This change removes the entire io::lazy module in favour of SyncOnceCell. SyncOnceCell's fast path is much faster (a single atomic operation) than locking a sys_common::Mutex on every access like Lazy did. However, SyncOnceCell does not use atexit() to drop the contained object during shutdown. As noted above, this is not a problem for stdin. It simply means stdin is now usable during shutdown. The atexit() call for stdout is moved to the stdio module. Unlike the now-removed Lazy struct, SyncOnceCell does not have a 'gone and unusable' state that panics. Instead of adding this again, this simply replaces the buffer with one with zero capacity. This effectively flushes the old buffer *and* makes any writes afterwards pass through directly without touching a buffer, making print!() available during shutdown without panicking. --- In addition, because the contents of the SyncOnceCell are no longer dropped, we can now use `&'static` instead of `Arc` in `Stdout` and `Stdin`. This also saves two levels of indirection in `stdin()` and `stdout()`, since Lazy effectively stored a `Box<Arc<T>>`, and SyncOnceCell stores the `T` directly.
-rw-r--r--library/std/src/io/lazy.rs63
-rw-r--r--library/std/src/io/mod.rs1
-rw-r--r--library/std/src/io/stdio.rs76
-rw-r--r--src/test/ui/stdout-during-shutdown.rs19
-rw-r--r--src/test/ui/stdout-during-shutdown.run.stdout1
5 files changed, 60 insertions, 100 deletions
diff --git a/library/std/src/io/lazy.rs b/library/std/src/io/lazy.rs
deleted file mode 100644
index 1968d49..0000000
--- a/library/std/src/io/lazy.rs
+++ /dev/null
@@ -1,63 +0,0 @@
1use crate::cell::Cell;
2use crate::ptr;
3use crate::sync::Arc;
4use crate::sys_common;
5use crate::sys_common::mutex::Mutex;
6
7pub struct Lazy<T> {
8 // We never call `lock.init()`, so it is UB to attempt to acquire this mutex reentrantly!
9 lock: Mutex,
10 ptr: Cell<*mut Arc<T>>,
11}
12
13#[inline]
14const fn done<T>() -> *mut Arc<T> {
15 1_usize as *mut _
16}
17
18unsafe impl<T> Sync for Lazy<T> {}
19
20impl<T> Lazy<T> {
21 pub const fn new() -> Lazy<T> {
22 Lazy { lock: Mutex::new(), ptr: Cell::new(ptr::null_mut()) }
23 }
24}
25
26impl<T: Send + Sync + 'static> Lazy<T> {
27 /// Safety: `init` must not call `get` on the variable that is being
28 /// initialized.
29 pub unsafe fn get(&'static self, init: fn() -> Arc<T>) -> Option<Arc<T>> {
30 let _guard = self.lock.lock();
31 let ptr = self.ptr.get();
32 if ptr.is_null() {
33 Some(self.init(init))
34 } else if ptr == done() {
35 None
36 } else {
37 Some((*ptr).clone())
38 }
39 }
40
41 // Must only be called with `lock` held
42 unsafe fn init(&'static self, init: fn() -> Arc<T>) -> Arc<T> {
43 // If we successfully register an at exit handler, then we cache the
44 // `Arc` allocation in our own internal box (it will get deallocated by
45 // the at exit handler). Otherwise we just return the freshly allocated
46 // `Arc`.
47 let registered = sys_common::at_exit(move || {
48 let ptr = {
49 let _guard = self.lock.lock();
50 self.ptr.replace(done())
51 };
52 drop(Box::from_raw(ptr))
53 });
54 // This could reentrantly call `init` again, which is a problem
55 // because our `lock` allows reentrancy!
56 // That's why `get` is unsafe and requires the caller to ensure no reentrancy happens.
57 let ret = init();
58 if registered.is_ok() {
59 self.ptr.set(Box::into_raw(Box::new(ret.clone())));
60 }
61 ret
62 }
63}
diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs
index adea8a8..d9d0380 100644
--- a/library/std/src/io/mod.rs
+++ b/library/std/src/io/mod.rs
@@ -285,7 +285,6 @@ mod buffered;
285mod cursor; 285mod cursor;
286mod error; 286mod error;
287mod impls; 287mod impls;
288mod lazy;
289pub mod prelude; 288pub mod prelude;
290mod stdio; 289mod stdio;
291mod util; 290mod util;
diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs
index 71ed2d8..61ccc6f 100644
--- a/library/std/src/io/stdio.rs
+++ b/library/std/src/io/stdio.rs
@@ -7,10 +7,11 @@ use crate::io::prelude::*;
7 7
8use crate::cell::RefCell; 8use crate::cell::RefCell;
9use crate::fmt; 9use crate::fmt;
10use crate::io::lazy::Lazy;
11use crate::io::{self, BufReader, Initializer, IoSlice, IoSliceMut, LineWriter}; 10use crate::io::{self, BufReader, Initializer, IoSlice, IoSliceMut, LineWriter};
12use crate::sync::{Arc, Mutex, MutexGuard, Once}; 11use crate::lazy::SyncOnceCell;
12use crate::sync::{Mutex, MutexGuard};
13use crate::sys::stdio; 13use crate::sys::stdio;
14use crate::sys_common;
14use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard}; 15use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
15use crate::thread::LocalKey; 16use crate::thread::LocalKey;
16 17
@@ -217,7 +218,7 @@ fn handle_ebadf<T>(r: io::Result<T>, default: T) -> io::Result<T> {
217/// ``` 218/// ```
218#[stable(feature = "rust1", since = "1.0.0")] 219#[stable(feature = "rust1", since = "1.0.0")]
219pub struct Stdin { 220pub struct Stdin {
220 inner: Arc<Mutex<BufReader<StdinRaw>>>, 221 inner: &'static Mutex<BufReader<StdinRaw>>,
221} 222}
222 223
223/// A locked reference to the `Stdin` handle. 224/// A locked reference to the `Stdin` handle.
@@ -292,15 +293,11 @@ pub struct StdinLock<'a> {
292/// ``` 293/// ```
293#[stable(feature = "rust1", since = "1.0.0")] 294#[stable(feature = "rust1", since = "1.0.0")]
294pub fn stdin() -> Stdin { 295pub fn stdin() -> Stdin {
295 static INSTANCE: Lazy<Mutex<BufReader<StdinRaw>>> = Lazy::new(); 296 static INSTANCE: SyncOnceCell<Mutex<BufReader<StdinRaw>>> = SyncOnceCell::new();
296 return Stdin { 297 Stdin {
297 inner: unsafe { INSTANCE.get(stdin_init).expect("cannot access stdin during shutdown") }, 298 inner: INSTANCE.get_or_init(|| {
298 }; 299 Mutex::new(BufReader::with_capacity(stdio::STDIN_BUF_SIZE, stdin_raw()))
299 300 }),
300 fn stdin_init() -> Arc<Mutex<BufReader<StdinRaw>>> {
301 // This must not reentrantly access `INSTANCE`
302 let stdin = stdin_raw();
303 Arc::new(Mutex::new(BufReader::with_capacity(stdio::STDIN_BUF_SIZE, stdin)))
304 } 301 }
305} 302}
306 303
@@ -476,7 +473,7 @@ pub struct Stdout {
476 // FIXME: this should be LineWriter or BufWriter depending on the state of 473 // FIXME: this should be LineWriter or BufWriter depending on the state of
477 // stdout (tty or not). Note that if this is not line buffered it 474 // stdout (tty or not). Note that if this is not line buffered it
478 // should also flush-on-panic or some form of flush-on-abort. 475 // should also flush-on-panic or some form of flush-on-abort.
479 inner: Arc<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>>, 476 inner: &'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>,
480} 477}
481 478
482/// A locked reference to the `Stdout` handle. 479/// A locked reference to the `Stdout` handle.
@@ -534,19 +531,27 @@ pub struct StdoutLock<'a> {
534/// ``` 531/// ```
535#[stable(feature = "rust1", since = "1.0.0")] 532#[stable(feature = "rust1", since = "1.0.0")]
536pub fn stdout() -> Stdout { 533pub fn stdout() -> Stdout {
537 static INSTANCE: Lazy<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = Lazy::new(); 534 static INSTANCE: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> =
538 return Stdout { 535 SyncOnceCell::new();
539 inner: unsafe { INSTANCE.get(stdout_init).expect("cannot access stdout during shutdown") }, 536 Stdout {
540 }; 537 inner: INSTANCE.get_or_init(|| unsafe {
541 538 let _ = sys_common::at_exit(|| {
542 fn stdout_init() -> Arc<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> { 539 if let Some(instance) = INSTANCE.get() {
543 // This must not reentrantly access `INSTANCE` 540 // Flush the data and disable buffering during shutdown
544 let stdout = stdout_raw(); 541 // by replacing the line writer by one with zero
545 unsafe { 542 // buffering capacity.
546 let ret = Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout)))); 543 // We use try_lock() instead of lock(), because someone
547 ret.init(); 544 // might have leaked a StdoutLock, which would
548 ret 545 // otherwise cause a deadlock here.
549 } 546 if let Some(lock) = instance.try_lock() {
547 *lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
548 }
549 }
550 });
551 let r = ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw())));
552 r.init();
553 r
554 }),
550 } 555 }
551} 556}
552 557
@@ -741,16 +746,15 @@ pub fn stderr() -> Stderr {
741 // 746 //
742 // This has the added benefit of allowing `stderr` to be usable during 747 // This has the added benefit of allowing `stderr` to be usable during
743 // process shutdown as well! 748 // process shutdown as well!
744 static INSTANCE: ReentrantMutex<RefCell<StderrRaw>> = 749 static INSTANCE: SyncOnceCell<ReentrantMutex<RefCell<StderrRaw>>> = SyncOnceCell::new();
745 unsafe { ReentrantMutex::new(RefCell::new(stderr_raw())) }; 750
746 751 Stderr {
747 // When accessing stderr we need one-time initialization of the reentrant 752 inner: INSTANCE.get_or_init(|| unsafe {
748 // mutex. Afterwards we can just always use the now-filled-in `INSTANCE` value. 753 let r = ReentrantMutex::new(RefCell::new(stderr_raw()));
749 static INIT: Once = Once::new(); 754 r.init();
750 INIT.call_once(|| unsafe { 755 r
751 INSTANCE.init(); 756 }),
752 }); 757 }
753 Stderr { inner: &INSTANCE }
754} 758}
755 759
756impl Stderr { 760impl Stderr {
diff --git a/src/test/ui/stdout-during-shutdown.rs b/src/test/ui/stdout-during-shutdown.rs
new file mode 100644
index 0000000..a6cf812
--- /dev/null
+++ b/src/test/ui/stdout-during-shutdown.rs
@@ -0,0 +1,19 @@
1// run-pass
2// check-run-results
3// ignore-emscripten
4
5// Emscripten doesn't flush its own stdout buffers on exit, which would fail
6// this test. So this test is disabled on this platform.
7// See https://emscripten.org/docs/getting_started/FAQ.html#what-does-exiting-the-runtime-mean-why-don-t-atexit-s-run
8
9#![feature(rustc_private)]
10
11extern crate libc;
12
13fn main() {
14 extern "C" fn bye() {
15 print!(", world!");
16 }
17 unsafe { libc::atexit(bye) };
18 print!("hello");
19}
diff --git a/src/test/ui/stdout-during-shutdown.run.stdout b/src/test/ui/stdout-during-shutdown.run.stdout
new file mode 100644
index 0000000..30f51a3
--- /dev/null
+++ b/src/test/ui/stdout-during-shutdown.run.stdout
@@ -0,0 +1 @@
hello, world! \ No newline at end of file