Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Rewrite io::lazy
  • Loading branch information
pitdicker committed Feb 28, 2019
commit 271d990bf602b629007a8b8eb659197079358219
101 changes: 57 additions & 44 deletions src/libstd/io/lazy.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,77 @@
use crate::cell::Cell;
use crate::ptr;
use crate::sync::Arc;
use crate::cell::UnsafeCell;
use crate::sync::atomic::AtomicUsize;
use crate::sync::atomic::Ordering;
use crate::sys_common;
use crate::sys_common::mutex::Mutex;

/// Helper for lazy initialization of a static, with a destructor that runs when the main (Rust)
/// thread exits.
///
/// Currently used only inside the standard library, by the stdio types.
///
/// # Safety
/// - `UnsafeCell`: We only create a mutable reference during initialization and during the shutdown
/// phase. At both times there can't exist any other references.
/// - Destruction. The `Drop` implementation of `T` should not access references to anything except
/// itself, they are not guaranteed to exist. It should also not rely on other machinery of the
/// standard library to be available.
/// - Initialization. The `init` function for `get` should not call `get` itself, to prevent
/// infinite recursion and acquiring the guard mutex reentrantly.
/// - We use the `Mutex` from `sys::common` because it has a `const` constructor. It currently has
/// UB when acquired reentrantly without calling `init`.
pub struct Lazy<T> {
// We never call `lock.init()`, so it is UB to attempt to acquire this mutex reentrantly!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You removed this comment, and the one below about reentrancy -- but it doesn't seem like guard.init() is ever actually called?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should be left until we merge #56410 I guess. After that there will not be any init() method and it would not be UB to call it reentrantly even (it would deadlock instead).

These mutexes are not used reentrantly, so it's fine to not call init() now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only moved to the top of the file, hopefully the warning is enough there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These mutexes are not used reentrantly, so it's fine to not call init() now.

This relies on the users of this abstractions -- but the comment saying so has been removed from get.

It is only moved to the top of the file, hopefully the warning is enough there.

When I added these comments I systematically added them next to the place where the Mutex is declared. I think this consistency is helpful.

lock: Mutex,
ptr: Cell<*mut Arc<T>>,
guard: Mutex, // Only used to protect initialization.
status: AtomicUsize,
data: UnsafeCell<Option<T>>,
}

#[inline]
const fn done<T>() -> *mut Arc<T> { 1_usize as *mut _ }

unsafe impl<T> Sync for Lazy<T> {}

const UNINITIALIZED: usize = 0;
const SHUTDOWN: usize = 1;
const AVAILABLE: usize = 2;

impl<T> Lazy<T> {
pub const fn new() -> Lazy<T> {
Lazy {
lock: Mutex::new(),
ptr: Cell::new(ptr::null_mut()),
guard: Mutex::new(),
status: AtomicUsize::new(UNINITIALIZED),
data: UnsafeCell::new(None),
}
}
}

impl<T: Send + Sync + 'static> Lazy<T> {
/// Safety: `init` must not call `get` on the variable that is being
/// initialized.
pub unsafe fn get(&'static self, init: fn() -> Arc<T>) -> Option<Arc<T>> {
let _guard = self.lock.lock();
let ptr = self.ptr.get();
if ptr.is_null() {
Some(self.init(init))
} else if ptr == done() {
None
} else {
Some((*ptr).clone())
}
}
pub unsafe fn get(&'static self, init: fn() -> T) -> Option<&T> {
match self.status.load(Ordering::Acquire) {
UNINITIALIZED => {
let _guard = self.guard.lock();
// Double-check to make sure this `Lazy` didn't get initialized by another
// thread in the small window before we acquired the mutex.
if self.status.load(Ordering::Relaxed) != UNINITIALIZED {
return self.get(init);
}

// Register an `at_exit` handler that drops `data` when the main thread exits.
let registered = sys_common::at_exit(move || {
*self.data.get() = None; // `T` gets dropped here
self.status.store(SHUTDOWN, Ordering::Release);
});
if registered.is_err() {
// Registering the handler will only fail if we are already in the shutdown
// phase. In that case don't attempt to initialize.
self.status.store(SHUTDOWN, Ordering::Release);
return None;
}

// Run the initializer of `T`.
*self.data.get() = Some(init());
self.status.store(AVAILABLE, Ordering::Release);

// Must only be called with `lock` held
unsafe fn init(&'static self, init: fn() -> Arc<T>) -> Arc<T> {
// If we successfully register an at exit handler, then we cache the
// `Arc` allocation in our own internal box (it will get deallocated by
// the at exit handler). Otherwise we just return the freshly allocated
// `Arc`.
let registered = sys_common::at_exit(move || {
let ptr = {
let _guard = self.lock.lock();
self.ptr.replace(done())
};
drop(Box::from_raw(ptr))
});
// This could reentrantly call `init` again, which is a problem
// because our `lock` allows reentrancy!
// That's why `get` is unsafe and requires the caller to ensure no reentrancy happens.
let ret = init();
if registered.is_ok() {
self.ptr.set(Box::into_raw(Box::new(ret.clone())));
(*self.data.get()).as_ref()
},
SHUTDOWN => None,
_ => (*self.data.get()).as_ref(),
}
ret
}
}
64 changes: 30 additions & 34 deletions src/libstd/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::cell::RefCell;
use crate::fmt;
use crate::io::lazy::Lazy;
use crate::io::{self, Initializer, BufReader, LineWriter};
use crate::sync::{Arc, Mutex, MutexGuard};
use crate::sync::{Mutex, MutexGuard};
use crate::sys::stdio;
use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
use crate::thread::LocalKey;
Expand Down Expand Up @@ -138,7 +138,7 @@ fn handle_ebadf<T>(r: io::Result<T>, default: T) -> io::Result<T> {
/// an error.
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Stdin {
inner: Arc<Mutex<BufReader<Maybe<StdinRaw>>>>,
inner: &'static Mutex<BufReader<Maybe<StdinRaw>>>,
}

/// A locked reference to the `Stdin` handle.
Expand Down Expand Up @@ -202,21 +202,19 @@ pub struct StdinLock<'a> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stdin() -> Stdin {
static INSTANCE: Lazy<Mutex<BufReader<Maybe<StdinRaw>>>> = Lazy::new();
return Stdin {
inner: unsafe {
INSTANCE.get(stdin_init).expect("cannot access stdin during shutdown")
},
};

fn stdin_init() -> Arc<Mutex<BufReader<Maybe<StdinRaw>>>> {
// This must not reentrantly access `INSTANCE`
static STDIN: Lazy<Mutex<BufReader<Maybe<StdinRaw>>>> = Lazy::new();
fn stdin_init() -> Mutex<BufReader<Maybe<StdinRaw>>> {
let stdin = match stdin_raw() {
Ok(stdin) => Maybe::Real(stdin),
_ => Maybe::Fake
};
Mutex::new(BufReader::with_capacity(stdio::STDIN_BUF_SIZE, stdin))
}

Arc::new(Mutex::new(BufReader::with_capacity(stdio::STDIN_BUF_SIZE, stdin)))
Stdin {
inner: unsafe {
STDIN.get(stdin_init).expect("cannot access stdin during shutdown")
},
}
}

Expand Down Expand Up @@ -355,7 +353,7 @@ pub struct Stdout {
// FIXME: this should be LineWriter or BufWriter depending on the state of
// stdout (tty or not). Note that if this is not line buffered it
// should also flush-on-panic or some form of flush-on-abort.
inner: Arc<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>>,
inner: &'static ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>,
}

/// A locked reference to the `Stdout` handle.
Expand Down Expand Up @@ -418,20 +416,19 @@ pub struct StdoutLock<'a> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stdout() -> Stdout {
static INSTANCE: Lazy<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>> = Lazy::new();
return Stdout {
inner: unsafe {
INSTANCE.get(stdout_init).expect("cannot access stdout during shutdown")
},
};

fn stdout_init() -> Arc<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>> {
// This must not reentrantly access `INSTANCE`
static STDOUT: Lazy<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>> = Lazy::new();
fn stdout_init() -> ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>> {
let stdout = match stdout_raw() {
Ok(stdout) => Maybe::Real(stdout),
_ => Maybe::Fake,
};
Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout))))
ReentrantMutex::new(RefCell::new(LineWriter::new(stdout)))
}

Stdout {
inner: unsafe {
STDOUT.get(stdout_init).expect("cannot access stdout during shutdown")
},
}
}

Expand Down Expand Up @@ -513,7 +510,7 @@ impl fmt::Debug for StdoutLock<'_> {
/// an error.
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Stderr {
inner: Arc<ReentrantMutex<RefCell<Maybe<StderrRaw>>>>,
inner: &'static ReentrantMutex<RefCell<Maybe<StderrRaw>>>,
}

/// A locked reference to the `Stderr` handle.
Expand Down Expand Up @@ -571,20 +568,19 @@ pub struct StderrLock<'a> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stderr() -> Stderr {
static INSTANCE: Lazy<ReentrantMutex<RefCell<Maybe<StderrRaw>>>> = Lazy::new();
return Stderr {
inner: unsafe {
INSTANCE.get(stderr_init).expect("cannot access stderr during shutdown")
},
};

fn stderr_init() -> Arc<ReentrantMutex<RefCell<Maybe<StderrRaw>>>> {
// This must not reentrantly access `INSTANCE`
static STDERR: Lazy<ReentrantMutex<RefCell<Maybe<StderrRaw>>>> = Lazy::new();
fn stderr_init() -> ReentrantMutex<RefCell<Maybe<StderrRaw>>> {
let stderr = match stderr_raw() {
Ok(stderr) => Maybe::Real(stderr),
_ => Maybe::Fake,
};
Arc::new(ReentrantMutex::new(RefCell::new(stderr)))
ReentrantMutex::new(RefCell::new(stderr))
}

Stderr {
inner: unsafe {
STDERR.get(stderr_init).expect("cannot access stderr during shutdown")
},
}
}

Expand Down