Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add buffering to stdout #115652

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
108 changes: 97 additions & 11 deletions library/std/src/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::io::prelude::*;
use crate::cell::{Cell, RefCell};
use crate::fmt;
use crate::fs::File;
use crate::io::{self, BorrowedCursor, BufReader, IoSlice, IoSliceMut, LineWriter, Lines};
use crate::io::{
self, BorrowedCursor, BufReader, BufWriter, IoSlice, IoSliceMut, LineWriter, Lines,
};
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sync::{Arc, Mutex, MutexGuard, OnceLock, ReentrantMutex, ReentrantMutexGuard};
use crate::sys::stdio;
Expand Down Expand Up @@ -509,6 +511,82 @@ impl fmt::Debug for StdinLock<'_> {
}
}

/// Either a [`LineWriter`] or [`BufWriter`] wrapping a [`StdoutRaw`].
///
/// Standard output is line-buffered when outputting to a terminal and normally buffered otherwise.
enum StdoutWriter {
LineWriter(LineWriter<StdoutRaw>),
BufWriter(BufWriter<StdoutRaw>),
}

impl StdoutWriter {
/// Creates a new `StdoutWriter`. Will check if the output is a terminal and use the appropriate writer.
fn new(stdout: StdoutRaw) -> StdoutWriter {
if stdout.0.is_terminal() {
StdoutWriter::LineWriter(LineWriter::new(stdout))
} else {
StdoutWriter::BufWriter(BufWriter::new(stdout))
}
}

/// Creates a new `StdoutWriter` with at least the specified capacity for the internal buffer.
///
/// Will check if the output is a terminal and use the appropriate writer.
fn with_capacity(capacity: usize, stdout: StdoutRaw) -> StdoutWriter {
if stdout.0.is_terminal() {
StdoutWriter::LineWriter(LineWriter::with_capacity(capacity, stdout))
} else {
StdoutWriter::BufWriter(BufWriter::with_capacity(capacity, stdout))
}
}
}

impl Write for StdoutWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
StdoutWriter::LineWriter(writer) => writer.write(buf),
StdoutWriter::BufWriter(writer) => writer.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
StdoutWriter::LineWriter(writer) => writer.flush(),
StdoutWriter::BufWriter(writer) => writer.flush(),
}
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
match self {
StdoutWriter::LineWriter(writer) => writer.write_all(buf),
StdoutWriter::BufWriter(writer) => writer.write_all(buf),
}
}
fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> io::Result<()> {
match self {
StdoutWriter::LineWriter(writer) => writer.write_fmt(fmt),
StdoutWriter::BufWriter(writer) => writer.write_fmt(fmt),
}
}
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
match self {
StdoutWriter::LineWriter(writer) => writer.write_vectored(bufs),
StdoutWriter::BufWriter(writer) => writer.write_vectored(bufs),
}
}
#[inline]
fn is_write_vectored(&self) -> bool {
match self {
StdoutWriter::LineWriter(writer) => writer.is_write_vectored(),
StdoutWriter::BufWriter(writer) => writer.is_write_vectored(),
}
}
fn write_all_vectored(&mut self, bufs: &mut [io::IoSlice<'_>]) -> io::Result<()> {
match self {
StdoutWriter::LineWriter(writer) => writer.write_all_vectored(bufs),
StdoutWriter::BufWriter(writer) => writer.write_all_vectored(bufs),
}
}
}

/// A handle to the global standard output stream of the current process.
///
/// Each handle shares a global buffer of data to be written to the standard
Expand All @@ -533,10 +611,7 @@ impl fmt::Debug for StdinLock<'_> {
/// [`io::stdout`]: stdout
#[stable(feature = "rust1", since = "1.0.0")]
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: &'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>,
inner: &'static ReentrantMutex<RefCell<StdoutWriter>>,
}

/// A locked reference to the [`Stdout`] handle.
Expand All @@ -558,10 +633,10 @@ pub struct Stdout {
#[must_use = "if unused stdout will immediately unlock"]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct StdoutLock<'a> {
inner: ReentrantMutexGuard<'a, RefCell<LineWriter<StdoutRaw>>>,
inner: ReentrantMutexGuard<'a, RefCell<StdoutWriter>>,
}

static STDOUT: OnceLock<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = OnceLock::new();
static STDOUT: OnceLock<ReentrantMutex<RefCell<StdoutWriter>>> = OnceLock::new();

/// Constructs a new handle to the standard output of the current process.
///
Expand Down Expand Up @@ -614,7 +689,7 @@ static STDOUT: OnceLock<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = OnceLo
pub fn stdout() -> Stdout {
Stdout {
inner: STDOUT
.get_or_init(|| ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw())))),
.get_or_init(|| ReentrantMutex::new(RefCell::new(StdoutWriter::new(stdout_raw())))),
}
}

Expand All @@ -625,7 +700,7 @@ pub fn cleanup() {
let mut initialized = false;
let stdout = STDOUT.get_or_init(|| {
initialized = true;
ReentrantMutex::new(RefCell::new(LineWriter::with_capacity(0, stdout_raw())))
ReentrantMutex::new(RefCell::new(StdoutWriter::with_capacity(0, stdout_raw())))
});

if !initialized {
Expand All @@ -634,7 +709,7 @@ pub fn cleanup() {
// might have leaked a StdoutLock, which would
// otherwise cause a deadlock here.
if let Some(lock) = stdout.try_lock() {
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
*lock.borrow_mut() = StdoutWriter::with_capacity(0, stdout_raw());
}
}
}
Expand Down Expand Up @@ -1082,7 +1157,18 @@ macro_rules! impl_is_terminal {
)*}
}

impl_is_terminal!(File, Stdin, StdinLock<'_>, Stdout, StdoutLock<'_>, Stderr, StderrLock<'_>);
impl_is_terminal!(
File,
Stdin,
StdinLock<'_>,
Stdout,
StdoutLock<'_>,
Stderr,
StderrLock<'_>,
stdio::Stdin,
stdio::Stdout,
stdio::Stderr
);

#[unstable(
feature = "print_internals",
Expand Down
28 changes: 28 additions & 0 deletions library/std/src/os/fd/owned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::marker::PhantomData;
use crate::mem::forget;
#[cfg(not(any(target_arch = "wasm32", target_env = "sgx", target_os = "hermit")))]
use crate::sys::cvt;
use crate::sys::stdio;
use crate::sys_common::{AsInner, FromInner, IntoInner};

/// A borrowed file descriptor.
Expand Down Expand Up @@ -432,6 +433,15 @@ impl<'a> AsFd for io::StdinLock<'a> {
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsFd for stdio::Stdin {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
// SAFETY: user code should not close stdin out from under the standard library
unsafe { BorrowedFd::borrow_raw(0) }
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsFd for io::Stdout {
#[inline]
Expand All @@ -449,6 +459,15 @@ impl<'a> AsFd for io::StdoutLock<'a> {
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsFd for stdio::Stdout {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
// SAFETY: user code should not close stdout out from under the standard library
unsafe { BorrowedFd::borrow_raw(1) }
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsFd for io::Stderr {
#[inline]
Expand All @@ -465,3 +484,12 @@ impl<'a> AsFd for io::StderrLock<'a> {
unsafe { BorrowedFd::borrow_raw(2) }
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsFd for stdio::Stderr {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
// SAFETY: user code should not close stderr out from under the standard library
unsafe { BorrowedFd::borrow_raw(2) }
}
}
24 changes: 24 additions & 0 deletions library/std/src/os/windows/io/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,14 @@ impl<'a> AsHandle for crate::io::StdinLock<'a> {
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsHandle for crate::sys::stdio::Stdin {
#[inline]
fn as_handle(&self) -> BorrowedHandle<'_> {
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsHandle for crate::io::Stdout {
#[inline]
Expand All @@ -548,6 +556,14 @@ impl<'a> AsHandle for crate::io::StdoutLock<'a> {
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsHandle for crate::sys::stdio::Stdout {
#[inline]
fn as_handle(&self) -> BorrowedHandle<'_> {
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsHandle for crate::io::Stderr {
#[inline]
Expand All @@ -564,6 +580,14 @@ impl<'a> AsHandle for crate::io::StderrLock<'a> {
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsHandle for crate::sys::stdio::Stderr {
#[inline]
fn as_handle(&self) -> BorrowedHandle<'_> {
unsafe { BorrowedHandle::borrow_raw(self.as_raw_handle()) }
}
}

#[stable(feature = "io_safety", since = "1.63.0")]
impl AsHandle for crate::process::ChildStdin {
#[inline]
Expand Down
21 changes: 21 additions & 0 deletions library/std/src/os/windows/io/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,27 @@ impl AsRawHandle for io::Stderr {
}
}

#[stable(feature = "asraw_stdio", since = "1.21.0")]
impl AsRawHandle for sys::stdio::Stdin {
fn as_raw_handle(&self) -> RawHandle {
stdio_handle(unsafe { sys::c::GetStdHandle(sys::c::STD_INPUT_HANDLE) as RawHandle })
}
}

#[stable(feature = "asraw_stdio", since = "1.21.0")]
impl AsRawHandle for sys::stdio::Stdout {
fn as_raw_handle(&self) -> RawHandle {
stdio_handle(unsafe { sys::c::GetStdHandle(sys::c::STD_OUTPUT_HANDLE) as RawHandle })
}
}

#[stable(feature = "asraw_stdio", since = "1.21.0")]
impl AsRawHandle for sys::stdio::Stderr {
fn as_raw_handle(&self) -> RawHandle {
stdio_handle(unsafe { sys::c::GetStdHandle(sys::c::STD_ERROR_HANDLE) as RawHandle })
}
}

#[stable(feature = "asraw_stdio_locks", since = "1.35.0")]
impl<'a> AsRawHandle for io::StdinLock<'a> {
fn as_raw_handle(&self) -> RawHandle {
Expand Down
Loading