Skip to content

Commit

Permalink
Auto merge of #24029 - nagisa:print-locking, r=alexcrichton
Browse files Browse the repository at this point in the history
write_fmt calls write for each formatted field. The default implementation of write_fmt is used,
which will call write on not-yet-locked stdout (and write locking after), therefore making print!
in multithreaded environment still interleave contents of two separate prints.

I’m not sure whether we want to do this change, though, because it has the same deadlock hazard which we tried to avoid by not locking inside write_fmt itself (see [this comment](https://github.com/rust-lang/rust/blob/80def6c2447d23a624e611417f24cf0ab2a5a676/src/libstd/io/stdio.rs#L267)).

Spotted on [reddit].

cc @alexcrichton 

[reddit]: http://www.reddit.com/r/rust/comments/31comh/println_with_multiple_threads/
  • Loading branch information
bors committed Apr 8, 2015
2 parents 30e7e6e + 45aa6c8 commit ff80477
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 34 deletions.
42 changes: 25 additions & 17 deletions src/libstd/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use io::lazy::Lazy;
use io::{self, BufReader, LineWriter};
use sync::{Arc, Mutex, MutexGuard};
use sys::stdio;
use sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};

/// Stdout used by print! and println! macros
thread_local! {
Expand Down Expand Up @@ -210,7 +211,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<Mutex<LineWriter<StdoutRaw>>>,
inner: Arc<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>>,
}

/// A locked reference to the a `Stdout` handle.
Expand All @@ -219,7 +220,7 @@ pub struct Stdout {
/// method on `Stdout`.
#[stable(feature = "rust1", since = "1.0.0")]
pub struct StdoutLock<'a> {
inner: MutexGuard<'a, LineWriter<StdoutRaw>>,
inner: ReentrantMutexGuard<'a, RefCell<LineWriter<StdoutRaw>>>,
}

/// Constructs a new reference to the standard output of the current process.
Expand All @@ -231,13 +232,13 @@ pub struct StdoutLock<'a> {
/// The returned handle implements the `Write` trait.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stdout() -> Stdout {
static INSTANCE: Lazy<Mutex<LineWriter<StdoutRaw>>> = lazy_init!(stdout_init);
static INSTANCE: Lazy<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = lazy_init!(stdout_init);
return Stdout {
inner: INSTANCE.get().expect("cannot access stdout during shutdown"),
};

fn stdout_init() -> Arc<Mutex<LineWriter<StdoutRaw>>> {
Arc::new(Mutex::new(LineWriter::new(stdout_raw())))
fn stdout_init() -> Arc<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> {
Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw()))))
}
}

Expand All @@ -264,23 +265,26 @@ impl Write for Stdout {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.lock().write_all(buf)
}
// Don't override write_fmt as it's possible to run arbitrary code during a
// write_fmt, allowing the possibility of a recursive lock (aka deadlock)
fn write_fmt(&mut self, args: fmt::Arguments) -> io::Result<()> {
self.lock().write_fmt(args)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<'a> Write for StdoutLock<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(&buf[..cmp::min(buf.len(), OUT_MAX)])
self.inner.borrow_mut().write(&buf[..cmp::min(buf.len(), OUT_MAX)])
}
fn flush(&mut self) -> io::Result<()> {
self.inner.borrow_mut().flush()
}
fn flush(&mut self) -> io::Result<()> { self.inner.flush() }
}

/// A handle to the standard error stream of a process.
///
/// For more information, see `stderr`
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Stderr {
inner: Arc<Mutex<StderrRaw>>,
inner: Arc<ReentrantMutex<RefCell<StderrRaw>>>,
}

/// A locked reference to the a `Stderr` handle.
Expand All @@ -289,7 +293,7 @@ pub struct Stderr {
/// method on `Stderr`.
#[stable(feature = "rust1", since = "1.0.0")]
pub struct StderrLock<'a> {
inner: MutexGuard<'a, StderrRaw>,
inner: ReentrantMutexGuard<'a, RefCell<StderrRaw>>,
}

/// Constructs a new reference to the standard error stream of a process.
Expand All @@ -300,13 +304,13 @@ pub struct StderrLock<'a> {
/// The returned handle implements the `Write` trait.
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stderr() -> Stderr {
static INSTANCE: Lazy<Mutex<StderrRaw>> = lazy_init!(stderr_init);
static INSTANCE: Lazy<ReentrantMutex<RefCell<StderrRaw>>> = lazy_init!(stderr_init);
return Stderr {
inner: INSTANCE.get().expect("cannot access stderr during shutdown"),
};

fn stderr_init() -> Arc<Mutex<StderrRaw>> {
Arc::new(Mutex::new(stderr_raw()))
fn stderr_init() -> Arc<ReentrantMutex<RefCell<StderrRaw>>> {
Arc::new(ReentrantMutex::new(RefCell::new(stderr_raw())))
}
}

Expand All @@ -333,14 +337,18 @@ impl Write for Stderr {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.lock().write_all(buf)
}
// Don't override write_fmt for the same reasons as Stdout
fn write_fmt(&mut self, args: fmt::Arguments) -> io::Result<()> {
self.lock().write_fmt(args)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<'a> Write for StderrLock<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(&buf[..cmp::min(buf.len(), OUT_MAX)])
self.inner.borrow_mut().write(&buf[..cmp::min(buf.len(), OUT_MAX)])
}
fn flush(&mut self) -> io::Result<()> {
self.inner.borrow_mut().flush()
}
fn flush(&mut self) -> io::Result<()> { self.inner.flush() }
}

/// Resets the task-local stderr handle to the specified writer
Expand Down
6 changes: 3 additions & 3 deletions src/libstd/sync/condvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
use prelude::v1::*;

use sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use sync::poison::{self, LockResult};
use sys::time::SteadyTime;
use sync::{mutex, MutexGuard, PoisonError};
use sys_common::condvar as sys;
use sys_common::mutex as sys_mutex;
use sys_common::poison::{self, LockResult};
use sys::time::SteadyTime;
use time::Duration;
use sync::{mutex, MutexGuard, PoisonError};

/// A Condition Variable
///
Expand Down
13 changes: 6 additions & 7 deletions src/libstd/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@
pub use alloc::arc::{Arc, Weak};
pub use core::atomic;

pub use self::mutex::{Mutex, MutexGuard, StaticMutex};
pub use self::mutex::MUTEX_INIT;
pub use self::rwlock::{RwLock, StaticRwLock, RW_LOCK_INIT};
pub use self::rwlock::{RwLockReadGuard, RwLockWriteGuard};
pub use self::barrier::{Barrier, BarrierWaitResult};
pub use self::condvar::{Condvar, StaticCondvar, CONDVAR_INIT};
pub use self::mutex::MUTEX_INIT;
pub use self::mutex::{Mutex, MutexGuard, StaticMutex};
pub use self::once::{Once, ONCE_INIT};
pub use sys_common::poison::{PoisonError, TryLockError, TryLockResult, LockResult};
pub use self::rwlock::{RwLockReadGuard, RwLockWriteGuard};
pub use self::rwlock::{RwLock, StaticRwLock, RW_LOCK_INIT};
pub use self::semaphore::{Semaphore, SemaphoreGuard};
pub use self::barrier::{Barrier, BarrierWaitResult};
pub use self::poison::{PoisonError, TryLockError, TryLockResult, LockResult};

pub use self::future::Future;

Expand All @@ -39,6 +39,5 @@ mod condvar;
mod future;
mod mutex;
mod once;
mod poison;
mod rwlock;
mod semaphore;
6 changes: 3 additions & 3 deletions src/libstd/sync/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
use prelude::v1::*;

use cell::UnsafeCell;
use fmt;
use marker;
use ops::{Deref, DerefMut};
use sync::poison::{self, TryLockError, TryLockResult, LockResult};
use sys_common::mutex as sys;
use fmt;
use sys_common::poison::{self, TryLockError, TryLockResult, LockResult};

/// A mutual exclusion primitive useful for protecting shared data
///
Expand Down Expand Up @@ -212,7 +212,7 @@ impl<T> Mutex<T> {

/// Attempts to acquire this lock.
///
/// If the lock could not be acquired at this time, then `None` is returned.
/// If the lock could not be acquired at this time, then `Err` is returned.
/// Otherwise, an RAII guard is returned. The lock will be unlocked when the
/// guard is dropped.
///
Expand Down
4 changes: 2 additions & 2 deletions src/libstd/sync/rwlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
use prelude::v1::*;

use cell::UnsafeCell;
use fmt;
use marker;
use ops::{Deref, DerefMut};
use sync::poison::{self, LockResult, TryLockError, TryLockResult};
use sys_common::poison::{self, LockResult, TryLockError, TryLockResult};
use sys_common::rwlock as sys;
use fmt;

/// A reader-writer lock
///
Expand Down
2 changes: 2 additions & 0 deletions src/libstd/sys/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub mod condvar;
pub mod mutex;
pub mod net;
pub mod net2;
pub mod poison;
pub mod remutex;
pub mod rwlock;
pub mod stack;
pub mod thread;
Expand Down
File renamed without changes.
Loading

0 comments on commit ff80477

Please sign in to comment.