Skip to content

libstd: use block buffering when stdout is not a TTY #60904

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

Closed
wants to merge 3 commits into from
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
79 changes: 68 additions & 11 deletions src/libstd/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::io::prelude::*;
use crate::cell::RefCell;
use crate::fmt;
use crate::io::lazy::Lazy;
use crate::io::{self, Initializer, BufReader, LineWriter, IoSlice, IoSliceMut};
use crate::io::{self, Initializer, BufReader, BufWriter, LineWriter, IoSlice, IoSliceMut};
use crate::sync::{Arc, Mutex, MutexGuard};
use crate::sys::stdio;
use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
Expand Down Expand Up @@ -398,10 +398,7 @@ impl fmt::Debug for StdinLock<'_> {
/// [`io::stdout`]: fn.stdout.html
#[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: Arc<ReentrantMutex<RefCell<LineWriter<Maybe<StdoutRaw>>>>>,
inner: Arc<ReentrantMutex<RefCell<StdoutStream>>>,
}

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

/// Constructs a new handle to the standard output of the current process.
Expand Down Expand Up @@ -464,20 +461,80 @@ 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();
static INSTANCE: Lazy<ReentrantMutex<RefCell<StdoutStream>>> = 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>>>>> {
fn stdout_init() -> Arc<ReentrantMutex<RefCell<StdoutStream>>> {
// This must not reentrantly access `INSTANCE`
let stdout = match stdout_raw() {
Ok(stdout) => Maybe::Real(stdout),
_ => Maybe::Fake,
Ok(stdout) => {
let tty = stdout.0.is_tty();
let stdout = Maybe::Real(stdout);
if tty {
let inner = LineWriter::new(stdout);
StdoutStream::LineBuffered(inner)
} else {
let inner = BufWriter::with_capacity(stdio::STDOUT_BUF_SIZE, stdout);
StdoutStream::BlockBuffered(inner)
}
}
_ => StdoutStream::LineBuffered(LineWriter::new(Maybe::Fake)),
};
Arc::new(ReentrantMutex::new(RefCell::new(LineWriter::new(stdout))))
Arc::new(ReentrantMutex::new(RefCell::new(stdout)))
}
}

/// Container to allow switching between block buffering and line buffering for the process's
/// standard output.
enum StdoutStream {
BlockBuffered(BufWriter<Maybe<StdoutRaw>>),
LineBuffered(LineWriter<Maybe<StdoutRaw>>),
}

impl Write for StdoutStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
use self::StdoutStream::*;

match self {
BlockBuffered(w) => w.write(buf),
LineBuffered(w) => w.write(buf),
}
}
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
use self::StdoutStream::*;

match self {
BlockBuffered(w) => w.write_vectored(bufs),
LineBuffered(w) => w.write_vectored(bufs),
}
}
fn flush(&mut self) -> io::Result<()> {
use self::StdoutStream::*;

match self {
BlockBuffered(w) => w.flush(),
LineBuffered(w) => w.flush(),
}
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
use self::StdoutStream::*;

match self {
BlockBuffered(w) => w.write_all(buf),
LineBuffered(w) => w.write_all(buf)
}
}
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> io::Result<()> {
use self::StdoutStream::*;

match self {
BlockBuffered(w) => w.write_fmt(args),
LineBuffered(w) => w.write_fmt(args),
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/libstd/sys/cloudabi/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ impl Stdout {
pub fn new() -> io::Result<Stdout> {
Ok(Stdout(()))
}

pub fn is_tty(&self) -> bool {
false
}
}

impl io::Write for Stdout {
Expand Down Expand Up @@ -60,6 +64,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
}

pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
pub const STDOUT_BUF_SIZE: usize = 0;

pub fn panic_output() -> Option<impl io::Write> {
Stderr::new().ok()
Expand Down
10 changes: 10 additions & 0 deletions src/libstd/sys/redox/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ impl io::Read for Stdin {

impl Stdout {
pub fn new() -> io::Result<Stdout> { Ok(Stdout(())) }

pub fn is_tty(&self) -> bool {
if let Ok(fd) = syscall::dup(1, b"termios") {
let _ = syscall::close(fd);
true
} else {
false
}
}
}

impl io::Write for Stdout {
Expand Down Expand Up @@ -58,6 +67,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
}

pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
pub const STDOUT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;

pub fn panic_output() -> Option<impl io::Write> {
Stderr::new().ok()
Expand Down
6 changes: 6 additions & 0 deletions src/libstd/sys/sgx/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ impl io::Read for Stdin {

impl Stdout {
pub fn new() -> io::Result<Stdout> { Ok(Stdout(())) }

// FIXME: implement
pub fn is_tty(&self) -> bool {
false
}
}

impl io::Write for Stdout {
Expand Down Expand Up @@ -57,6 +62,7 @@ impl io::Write for Stderr {
}

pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
pub const STDOUT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;

pub fn is_ebadf(err: &io::Error) -> bool {
// FIXME: Rust normally maps Unix EBADF to `Other`
Expand Down
5 changes: 5 additions & 0 deletions src/libstd/sys/unix/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ impl io::Read for Stdin {

impl Stdout {
pub fn new() -> io::Result<Stdout> { Ok(Stdout(())) }

pub fn is_tty(&self) -> bool {
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
}

impl io::Write for Stdout {
Expand Down Expand Up @@ -61,6 +65,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
}

pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
pub const STDOUT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;

pub fn panic_output() -> Option<impl io::Write> {
Stderr::new().ok()
Expand Down
6 changes: 6 additions & 0 deletions src/libstd/sys/wasi/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ impl Stdout {
Ok(Stdout)
}

// FIXME: implement?
pub fn is_tty(&self) -> bool {
false
}

pub fn write(&self, data: &[u8]) -> io::Result<usize> {
self.write_vectored(&[IoSlice::new(data)])
}
Expand Down Expand Up @@ -71,6 +76,7 @@ impl io::Write for Stderr {
}

pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
pub const STDOUT_BUF_SIZE: usize = 0;

pub fn is_ebadf(err: &io::Error) -> bool {
err.raw_os_error() == Some(libc::__WASI_EBADF as i32)
Expand Down
5 changes: 5 additions & 0 deletions src/libstd/sys/wasm/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ impl Stdout {
pub fn new() -> io::Result<Stdout> {
Ok(Stdout)
}

pub fn is_tty(&self) -> bool {
false
}
}

impl io::Write for Stdout {
Expand Down Expand Up @@ -52,6 +56,7 @@ impl io::Write for Stderr {
}

pub const STDIN_BUF_SIZE: usize = 0;
pub const STDOUT_BUF_SIZE: usize = 0;

pub fn is_ebadf(_err: &io::Error) -> bool {
true
Expand Down
24 changes: 24 additions & 0 deletions src/libstd/sys/windows/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,30 @@ impl Stdout {
pub fn new() -> io::Result<Stdout> {
Ok(Stdout)
}

pub fn is_tty(&self) -> bool {
// Because these functions do not detect if the program is being run under a Cygwin/MSYS2
// TTY, we just try our best to see if we are being piped on Windows Console. If we can't
// tell, we fallback to assuming we are on a TTY
if let Ok(handle) = get_handle(c::STD_OUTPUT_HANDLE) {
if is_console(handle) {
true
} else {
// We need to check if at least one of the other stdio handles is connected to the
// console
let stdin = get_handle(c::STD_INPUT_HANDLE)
.map(is_console)
.unwrap_or(false);
let stderr = get_handle(c::STD_ERROR_HANDLE)
.map(is_console)
.unwrap_or(false);

!(stdin || stderr)
}
} else {
false
}
}
}

impl io::Write for Stdout {
Expand Down