diff --git a/library/std/src/io/mod.rs b/library/std/src/io/mod.rs index 00b85604a3f..9e8996a3e56 100644 --- a/library/std/src/io/mod.rs +++ b/library/std/src/io/mod.rs @@ -277,8 +277,12 @@ pub use self::error::{Error, ErrorKind, Result}; pub use self::stdio::set_output_capture; #[stable(feature = "rust1", since = "1.0.0")] pub use self::stdio::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; +#[unstable(feature = "stdio_locked", issue = "none")] +pub use self::stdio::{stderr_locked, stdin_locked, stdout_locked}; #[stable(feature = "rust1", since = "1.0.0")] pub use self::stdio::{StderrLock, StdinLock, StdoutLock}; +#[unstable(feature = "stdio_locked", issue = "none")] +pub use self::stdio::{StderrOwnedLock, StdinOwnedLock, StdoutOwnedLock}; #[unstable(feature = "print_internals", issue = "none")] pub use self::stdio::{_eprint, _print}; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/io/stdio.rs b/library/std/src/io/stdio.rs index 2b0d2b7e0be..026d2781263 100644 --- a/library/std/src/io/stdio.rs +++ b/library/std/src/io/stdio.rs @@ -261,6 +261,21 @@ pub struct StdinLock<'a> { inner: MutexGuard<'a, BufReader>, } +/// Owned locked [`Stdin`] handle, returned by [`Stdin::into_lock`] and +/// [`io::stdin_locked`]. +/// +/// This is exactly like [`StdinLock`], except that it can outlive the +/// [`Stdin`] handle that was used to create it. See the [`StdinLock`] +/// documentation for more details. +/// +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return +/// an error. +#[unstable(feature = "stdio_locked", issue = "none")] +pub type StdinOwnedLock = StdinLock<'static>; + /// Constructs a new handle to the standard input of the current process. /// /// Each handle returned is a reference to a shared global buffer whose access @@ -310,6 +325,48 @@ pub fn stdin() -> Stdin { } } +/// Constructs a new locked handle to the standard input of the current +/// process. +/// +/// Each handle returned is a guard granting locked access to a shared +/// global buffer whose access is synchronized via a mutex. If you need +/// more explicit control over locking, for example, in a multi-threaded +/// program, use the [`io::stdin`] function to obtain an unlocked handle, +/// along with the [`Stdin::lock`] method. +/// +/// The lock is released when the returned guard goes out of scope. The +/// returned guard also implements the [`Read`] and [`BufRead`] traits for +/// accessing the underlying data. +/// +/// **Note**: The mutex locked by this handle is not reentrant. Even in a +/// single-threaded program, calling other code that accesses [`Stdin`] +/// could cause a deadlock or panic, if this locked handle is held across +/// that call. +/// +/// ### Note: Windows Portability Consideration +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to read bytes that are not valid UTF-8 will return +/// an error. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(stdio_locked)] +/// use std::io::{self, Read}; +/// +/// fn main() -> io::Result<()> { +/// let mut buffer = String::new(); +/// let mut handle = io::stdin_locked(); +/// +/// handle.read_to_string(&mut buffer)?; +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "stdio_locked", issue = "none")] +pub fn stdin_locked() -> StdinOwnedLock { + stdin().into_lock() +} + impl Stdin { /// Locks this handle to the standard input stream, returning a readable /// guard. @@ -334,7 +391,7 @@ impl Stdin { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn lock(&self) -> StdinLock<'_> { - StdinLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) } + self.lock_any() } /// Locks this handle and reads a line of input, appending it to the specified buffer. @@ -367,6 +424,43 @@ impl Stdin { pub fn read_line(&self, buf: &mut String) -> io::Result { self.lock().read_line(buf) } + + // Locks this handle with any lifetime. This depends on the + // implementation detail that the underlying `Mutex` is static. + fn lock_any<'a>(&self) -> StdinLock<'a> { + StdinLock { inner: self.inner.lock().unwrap_or_else(|e| e.into_inner()) } + } + + /// Consumes this handle to the standard input stream, locking the + /// shared global buffer associated with the stream and returning a + /// readable guard. + /// + /// The lock is released when the returned guard goes out of scope. The + /// returned guard also implements the [`Read`] and [`BufRead`] traits + /// for accessing the underlying data. + /// + /// It is often simpler to directly get a locked handle using the + /// [`stdin_locked`] function instead, unless nearby code also needs to + /// use an unlocked handle. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(stdio_locked)] + /// use std::io::{self, Read}; + /// + /// fn main() -> io::Result<()> { + /// let mut buffer = String::new(); + /// let mut handle = io::stdin().into_lock(); + /// + /// handle.read_to_string(&mut buffer)?; + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "stdio_locked", issue = "none")] + pub fn into_lock(self) -> StdinOwnedLock { + self.lock_any() + } } #[stable(feature = "std_debug", since = "1.16.0")] @@ -507,6 +601,20 @@ pub struct StdoutLock<'a> { inner: ReentrantMutexGuard<'a, RefCell>>, } +/// Owned locked [`Stdout`] handle, returned by [`Stdout::into_lock`] and +/// [`io::stdout_locked`]. +/// +/// This is exactly like [`StdoutLock`], except that it can outlive the +/// [`Stdout`] handle that was used to create it. See the [`StdoutLock`] +/// documentation for more details. +/// +/// ### Note: Windows Portability Consideration +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +#[unstable(feature = "stdio_locked", issue = "none")] +pub type StdoutOwnedLock = StdoutLock<'static>; + static STDOUT: SyncOnceCell>>> = SyncOnceCell::new(); /// Constructs a new handle to the standard output of the current process. @@ -558,6 +666,42 @@ pub fn stdout() -> Stdout { } } +/// Constructs a new locked handle to the standard output of the current +/// process. +/// +/// Each handle returned is a guard granting locked access to a shared +/// global buffer whose access is synchronized via a mutex. If you need +/// more explicit control over locking, for example, in a multi-threaded +/// program, use the [`io::stdout`] function to obtain an unlocked handle, +/// along with the [`Stdout::lock`] method. +/// +/// The lock is released when the returned guard goes out of scope. The +/// returned guard also implements the [`Write`] trait for writing data. +/// +/// ### Note: Windows Portability Consideration +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(stdio_locked)] +/// use std::io::{self, Write}; +/// +/// fn main() -> io::Result<()> { +/// let mut handle = io::stdout_locked(); +/// +/// handle.write_all(b"hello world")?; +/// +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "stdio_locked", issue = "none")] +pub fn stdout_locked() -> StdoutLock<'static> { + stdout().into_lock() +} + pub fn cleanup() { if let Some(instance) = STDOUT.get() { // Flush the data and disable buffering during shutdown @@ -595,8 +739,45 @@ impl Stdout { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn lock(&self) -> StdoutLock<'_> { + self.lock_any() + } + + // Locks this handle with any lifetime. This depends on the + // implementation detail that the underlying `ReentrantMutex` is + // static. + fn lock_any<'a>(&self) -> StdoutLock<'a> { StdoutLock { inner: self.inner.lock() } } + + /// Consumes this handle to the standard output stream, locking the + /// shared global buffer associated with the stream and returning a + /// writable guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the [`Write`] trait for writing data. + /// + /// It is often simpler to directly get a locked handle using the + /// [`io::stdout_locked`] function instead, unless nearby code also + /// needs to use an unlocked handle. + /// + /// # Examples + /// + /// ```no_run + /// #![feature(stdio_locked)] + /// use std::io::{self, Write}; + /// + /// fn main() -> io::Result<()> { + /// let mut handle = io::stdout().into_lock(); + /// + /// handle.write_all(b"hello world")?; + /// + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "stdio_locked", issue = "none")] + pub fn into_lock(self) -> StdoutOwnedLock { + self.lock_any() + } } #[stable(feature = "std_debug", since = "1.16.0")] @@ -717,6 +898,20 @@ pub struct StderrLock<'a> { inner: ReentrantMutexGuard<'a, RefCell>, } +/// Owned locked [`Stderr`] handle, returned by [`Stderr::into_lock`] and +/// [`io::stderr_locked`]. +/// +/// This is exactly like [`StderrLock`], except that it can outlive the the +/// [`Stderr`] handle that was used to create it. See the [`StderrLock`] +/// documentation for more details. +/// +/// ### Note: Windows Portability Consideration +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +#[unstable(feature = "stdio_locked", issue = "none")] +pub type StderrOwnedLock = StderrLock<'static>; + /// Constructs a new handle to the standard error of the current process. /// /// This handle is not buffered. @@ -769,6 +964,35 @@ pub fn stderr() -> Stderr { } } +/// Constructs a new locked handle to the standard error of the current +/// process. +/// +/// This handle is not buffered. +/// +/// ### Note: Windows Portability Consideration +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +/// +/// # Example +/// +/// ```no_run +/// #![feature(stdio_locked)] +/// use std::io::{self, Write}; +/// +/// fn main() -> io::Result<()> { +/// let mut handle = io::stderr_locked(); +/// +/// handle.write_all(b"hello world")?; +/// +/// Ok(()) +/// } +/// ``` +#[unstable(feature = "stdio_locked", issue = "none")] +pub fn stderr_locked() -> StderrOwnedLock { + stderr().into_lock() +} + impl Stderr { /// Locks this handle to the standard error stream, returning a writable /// guard. @@ -792,8 +1016,42 @@ impl Stderr { /// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn lock(&self) -> StderrLock<'_> { + self.lock_any() + } + + // Locks this handle with any lifetime. This depends on the + // implementation detail that the underlying `ReentrantMutex` is + // static. + fn lock_any<'a>(&self) -> StderrLock<'a> { StderrLock { inner: self.inner.lock() } } + + /// Locks and consumes this handle to the standard error stream, + /// returning a writable guard. + /// + /// The lock is released when the returned guard goes out of scope. The + /// returned guard also implements the [`Write`] trait for writing + /// data. + /// + /// # Examples + /// + /// ``` + /// #![feature(stdio_locked)] + /// use std::io::{self, Write}; + /// + /// fn foo() -> io::Result<()> { + /// let stderr = io::stderr(); + /// let mut handle = stderr.into_lock(); + /// + /// handle.write_all(b"hello world")?; + /// + /// Ok(()) + /// } + /// ``` + #[unstable(feature = "stdio_locked", issue = "none")] + pub fn into_lock(self) -> StderrOwnedLock { + self.lock_any() + } } #[stable(feature = "std_debug", since = "1.16.0")] diff --git a/library/std/src/io/stdio/tests.rs b/library/std/src/io/stdio/tests.rs index 04af500268f..d84537e54de 100644 --- a/library/std/src/io/stdio/tests.rs +++ b/library/std/src/io/stdio/tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::panic::{RefUnwindSafe, UnwindSafe}; +use crate::sync::mpsc::sync_channel; use crate::thread; #[test] @@ -45,3 +46,136 @@ fn panic_doesnt_poison() { let _a = stderr(); let _a = _a.lock(); } + +#[test] +fn stderr_owned_lock_static() { + assert_static::(); +} +#[test] +fn stdin_owned_lock_static() { + assert_static::(); +} +#[test] +fn stdout_owned_lock_static() { + assert_static::(); +} + +fn assert_static() {} + +#[test] +#[cfg_attr(target_os = "emscripten", ignore)] +fn test_lock_stderr() { + test_lock(stderr, stderr_locked); +} +#[test] +#[cfg_attr(target_os = "emscripten", ignore)] +fn test_lock_stdin() { + test_lock(stdin, stdin_locked); +} +#[test] +#[cfg_attr(target_os = "emscripten", ignore)] +fn test_lock_stdout() { + test_lock(stdout, stdout_locked); +} + +// Helper trait to make lock testing function generic. +trait Stdio<'a>: 'static +where + Self::Lock: 'a, +{ + type Lock; + fn lock(&'a self) -> Self::Lock; +} +impl<'a> Stdio<'a> for Stderr { + type Lock = StderrLock<'a>; + fn lock(&'a self) -> StderrLock<'a> { + self.lock() + } +} +impl<'a> Stdio<'a> for Stdin { + type Lock = StdinLock<'a>; + fn lock(&'a self) -> StdinLock<'a> { + self.lock() + } +} +impl<'a> Stdio<'a> for Stdout { + type Lock = StdoutLock<'a>; + fn lock(&'a self) -> StdoutLock<'a> { + self.lock() + } +} + +// Helper trait to make lock testing function generic. +trait StdioOwnedLock: 'static {} +impl StdioOwnedLock for StderrOwnedLock {} +impl StdioOwnedLock for StdinOwnedLock {} +impl StdioOwnedLock for StdoutOwnedLock {} + +// Tests locking on stdio handles by starting two threads and checking that +// they block each other appropriately. +fn test_lock(get_handle: fn() -> T, get_locked: fn() -> U) +where + T: for<'a> Stdio<'a>, + U: StdioOwnedLock, +{ + // State enum to track different phases of the test, primarily when + // each lock is acquired and released. + #[derive(Debug, PartialEq)] + enum State { + Start1, + Acquire1, + Start2, + Release1, + Acquire2, + Release2, + } + use State::*; + // Logging vector to be checked to make sure lock acquisitions and + // releases happened in the correct order. + let log = Arc::new(Mutex::new(Vec::new())); + let ((tx1, rx1), (tx2, rx2)) = (sync_channel(0), sync_channel(0)); + let th1 = { + let (log, tx) = (Arc::clone(&log), tx1); + thread::spawn(move || { + log.lock().unwrap().push(Start1); + let handle = get_handle(); + { + let locked = handle.lock(); + log.lock().unwrap().push(Acquire1); + tx.send(Acquire1).unwrap(); // notify of acquisition + tx.send(Release1).unwrap(); // wait for release command + log.lock().unwrap().push(Release1); + } + tx.send(Acquire1).unwrap(); // wait for th2 acquire + { + let locked = handle.lock(); + log.lock().unwrap().push(Acquire1); + } + log.lock().unwrap().push(Release1); + }) + }; + let th2 = { + let (log, tx) = (Arc::clone(&log), tx2); + thread::spawn(move || { + tx.send(Start2).unwrap(); // wait for start command + let locked = get_locked(); + log.lock().unwrap().push(Acquire2); + tx.send(Acquire2).unwrap(); // notify of acquisition + tx.send(Release2).unwrap(); // wait for release command + log.lock().unwrap().push(Release2); + }) + }; + assert_eq!(rx1.recv().unwrap(), Acquire1); // wait for th1 acquire + log.lock().unwrap().push(Start2); + assert_eq!(rx2.recv().unwrap(), Start2); // block th2 + assert_eq!(rx1.recv().unwrap(), Release1); // release th1 + assert_eq!(rx2.recv().unwrap(), Acquire2); // wait for th2 acquire + assert_eq!(rx1.recv().unwrap(), Acquire1); // block th1 + assert_eq!(rx2.recv().unwrap(), Release2); // release th2 + th2.join().unwrap(); + th1.join().unwrap(); + assert_eq!( + *log.lock().unwrap(), + [Start1, Acquire1, Start2, Release1, Acquire2, Release2, Acquire1, Release1] + ); +}