Skip to content

Commit a69dca8

Browse files
committed
Implementation of Switchable Buffering for Stdout
1 parent 0cd939e commit a69dca8

File tree

10 files changed

+185
-18
lines changed

10 files changed

+185
-18
lines changed

library/std/src/io/buffered/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod bufreader;
44
mod bufwriter;
55
mod linewriter;
66
mod linewritershim;
7+
mod switchwriter;
78

89
#[cfg(test)]
910
mod tests;
@@ -19,6 +20,9 @@ use linewritershim::LineWriterShim;
1920
#[stable(feature = "bufwriter_into_parts", since = "1.56.0")]
2021
pub use bufwriter::WriterPanicked;
2122

23+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
24+
pub use switchwriter::{BufferMode, SwitchWriter};
25+
2226
/// An error returned by [`BufWriter::into_inner`] which combines an error that
2327
/// happened while writing out the buffer, and the buffered writer object
2428
/// which may be used to recover from the condition.
+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use crate::fmt::Arguments;
2+
use crate::io::{self, buffered::LineWriterShim, BufWriter, IoSlice, Write};
3+
/// Different buffering modes a writer can use
4+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
5+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6+
#[non_exhaustive]
7+
pub enum BufferMode {
8+
/// Immediate: forward writes directly to the underlying writer. In some
9+
/// cases, a writer may buffer temporarily to reduce the number of
10+
/// underlying writes (for instance, when processing a formatted write!(),
11+
/// which makes several tiny writes), but even in this case the formatted
12+
/// buffer will always be forwarded immediately.
13+
Immediate,
14+
15+
/// Block buffering: buffer writes until the buffer is full, then forward
16+
/// to the underlying writer
17+
Block,
18+
19+
/// Line buffering: same as block buffering, except that it immediately
20+
/// forwards the buffered content when it encounters a newline.
21+
Line,
22+
}
23+
24+
/// Wraps a writer and provides a switchable buffering mode for its output
25+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
26+
#[derive(Debug)]
27+
pub struct SwitchWriter<W: Write> {
28+
buffer: BufWriter<W>,
29+
mode: BufferMode,
30+
}
31+
32+
impl<W: Write> SwitchWriter<W> {
33+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
34+
pub fn new(writer: W, mode: BufferMode) -> Self {
35+
Self { buffer: BufWriter::new(writer), mode }
36+
}
37+
38+
// Don't forget to add with_capacity if this type ever becomes public
39+
40+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
41+
pub fn mode(&self) -> BufferMode {
42+
self.mode
43+
}
44+
45+
/// Set the buffering mode. This will not attempt any io; it only changes
46+
/// the mode used for subsequent writes.
47+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
48+
pub fn set_mode(&mut self, mode: BufferMode) {
49+
self.mode = mode
50+
}
51+
}
52+
53+
/// Shared logic for io methods that need to switch over the buffering mode
54+
macro_rules! use_correct_writer {
55+
($this:ident, |$writer:ident| $usage:expr) => {
56+
match $this.mode {
57+
BufferMode::Immediate => {
58+
$this.buffer.flush_buf()?;
59+
let $writer = $this.buffer.get_mut();
60+
$usage
61+
}
62+
BufferMode::Block => {
63+
let $writer = &mut $this.buffer;
64+
$usage
65+
}
66+
BufferMode::Line => {
67+
let mut $writer = LineWriterShim::new(&mut $this.buffer);
68+
$usage
69+
}
70+
}
71+
};
72+
}
73+
74+
impl<W: Write> Write for SwitchWriter<W> {
75+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
76+
use_correct_writer!(self, |writer| writer.write(buf))
77+
}
78+
79+
fn flush(&mut self) -> io::Result<()> {
80+
self.buffer.flush()
81+
}
82+
83+
fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
84+
use_correct_writer!(self, |writer| writer.write_vectored(bufs))
85+
}
86+
87+
fn is_write_vectored(&self) -> bool {
88+
self.buffer.is_write_vectored()
89+
}
90+
91+
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
92+
use_correct_writer!(self, |writer| writer.write_all(buf))
93+
}
94+
95+
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> {
96+
use_correct_writer!(self, |writer| writer.write_all_vectored(bufs))
97+
}
98+
99+
fn write_fmt(&mut self, fmt: Arguments<'_>) -> io::Result<()> {
100+
match self.mode {
101+
BufferMode::Immediate => {
102+
// write_fmt is usually going to be very numerous tiny writes
103+
// from the constituent fmt calls, so even though we're in
104+
// unbuffered mode we still collect it to the buffer so that
105+
// we can flush it in a single write.
106+
self.buffer.flush_buf()?;
107+
self.buffer.write_fmt(fmt)?;
108+
self.buffer.flush_buf()
109+
}
110+
BufferMode::Block => self.buffer.write_fmt(fmt),
111+
BufferMode::Line => {
112+
let mut shim = LineWriterShim::new(&mut self.buffer);
113+
shim.write_fmt(fmt)
114+
}
115+
}
116+
}
117+
}

library/std/src/io/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ use crate::str;
260260
use crate::sys;
261261
use crate::sys_common::memchr;
262262

263+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
264+
pub use self::buffered::BufferMode;
263265
#[stable(feature = "bufwriter_into_parts", since = "1.56.0")]
264266
pub use self::buffered::WriterPanicked;
265267
#[unstable(feature = "internal_output_capture", issue = "none")]

library/std/src/io/stdio.rs

+30-11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::io::prelude::*;
77

88
use crate::cell::{Cell, RefCell};
99
use crate::fmt;
10-
use crate::io::{self, BufReader, IoSlice, IoSliceMut, LineWriter, Lines};
10+
use crate::io::{self, buffered::SwitchWriter, BufReader, BufferMode, IoSlice, IoSliceMut, Lines};
1111
use crate::lazy::SyncOnceCell;
1212
use crate::pin::Pin;
1313
use crate::sync::atomic::{AtomicBool, Ordering};
@@ -524,10 +524,7 @@ impl fmt::Debug for StdinLock<'_> {
524524
/// [`io::stdout`]: stdout
525525
#[stable(feature = "rust1", since = "1.0.0")]
526526
pub struct Stdout {
527-
// FIXME: this should be LineWriter or BufWriter depending on the state of
528-
// stdout (tty or not). Note that if this is not line buffered it
529-
// should also flush-on-panic or some form of flush-on-abort.
530-
inner: Pin<&'static ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>>,
527+
inner: Pin<&'static ReentrantMutex<RefCell<SwitchWriter<StdoutRaw>>>>,
531528
}
532529

533530
/// A locked reference to the [`Stdout`] handle.
@@ -549,10 +546,10 @@ pub struct Stdout {
549546
#[must_use = "if unused stdout will immediately unlock"]
550547
#[stable(feature = "rust1", since = "1.0.0")]
551548
pub struct StdoutLock<'a> {
552-
inner: ReentrantMutexGuard<'a, RefCell<LineWriter<StdoutRaw>>>,
549+
inner: ReentrantMutexGuard<'a, RefCell<SwitchWriter<StdoutRaw>>>,
553550
}
554551

555-
static STDOUT: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = SyncOnceCell::new();
552+
static STDOUT: SyncOnceCell<ReentrantMutex<RefCell<SwitchWriter<StdoutRaw>>>> = SyncOnceCell::new();
556553

557554
/// Constructs a new handle to the standard output of the current process.
558555
///
@@ -605,7 +602,12 @@ static STDOUT: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = Sy
605602
pub fn stdout() -> Stdout {
606603
Stdout {
607604
inner: Pin::static_ref(&STDOUT).get_or_init_pin(
608-
|| unsafe { ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw()))) },
605+
|| unsafe {
606+
ReentrantMutex::new(RefCell::new(SwitchWriter::new(
607+
stdout_raw(),
608+
stdio::default_stdout_buffer_mode(),
609+
)))
610+
},
609611
|mutex| unsafe { mutex.init() },
610612
),
611613
}
@@ -614,13 +616,14 @@ pub fn stdout() -> Stdout {
614616
pub fn cleanup() {
615617
if let Some(instance) = STDOUT.get() {
616618
// Flush the data and disable buffering during shutdown
617-
// by replacing the line writer by one with zero
618-
// buffering capacity.
619619
// We use try_lock() instead of lock(), because someone
620620
// might have leaked a StdoutLock, which would
621621
// otherwise cause a deadlock here.
622622
if let Some(lock) = Pin::static_ref(instance).try_lock() {
623-
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
623+
if let Ok(mut instance) = lock.try_borrow_mut() {
624+
let _ = instance.flush();
625+
instance.set_mode(BufferMode::Immediate);
626+
}
624627
}
625628
}
626629
}
@@ -713,6 +716,20 @@ impl Write for &Stdout {
713716
}
714717
}
715718

719+
impl StdoutLock<'_> {
720+
/// Get the current global stdout buffering mode
721+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
722+
pub fn buffer_mode(&self) -> BufferMode {
723+
self.inner.borrow().mode()
724+
}
725+
726+
/// Set the current global stdout buffering mode
727+
#[unstable(feature = "stdout_switchable_buffering", issue = "78515")]
728+
pub fn set_buffer_mode(&mut self, mode: BufferMode) {
729+
self.inner.borrow_mut().set_mode(mode)
730+
}
731+
}
732+
716733
#[stable(feature = "rust1", since = "1.0.0")]
717734
impl Write for StdoutLock<'_> {
718735
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
@@ -734,6 +751,8 @@ impl Write for StdoutLock<'_> {
734751
fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> io::Result<()> {
735752
self.inner.borrow_mut().write_all_vectored(bufs)
736753
}
754+
// Don't specialize write_fmt because we need to be sure we don't
755+
// reentrantly call borrow_mut
737756
}
738757

739758
#[stable(feature = "std_debug", since = "1.16.0")]

library/std/src/sys/hermit/stdio.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use crate::io;
2-
use crate::io::{IoSlice, IoSliceMut};
1+
use crate::io::{self, BufferMode, IoSlice, IoSliceMut};
32
use crate::sys::hermit::abi;
43

54
pub struct Stdin;
@@ -118,3 +117,7 @@ pub fn is_ebadf(_err: &io::Error) -> bool {
118117
pub fn panic_output() -> Option<impl io::Write> {
119118
Some(Stderr::new())
120119
}
120+
121+
pub fn default_stdout_buffer_mode() -> BufferMode {
122+
BufferMode::Line
123+
}

library/std/src/sys/sgx/stdio.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use fortanix_sgx_abi as abi;
22

3-
use crate::io;
3+
use crate::io::{self, BufferMode};
44
#[cfg(not(test))]
55
use crate::slice;
66
#[cfg(not(test))]
@@ -73,6 +73,10 @@ pub fn panic_output() -> Option<impl io::Write> {
7373
super::abi::panic::SgxPanicOutput::new()
7474
}
7575

76+
pub fn default_stdout_buffer_mode() -> BufferMode {
77+
BufferMode::Line
78+
}
79+
7680
// This function is needed by libunwind. The symbol is named in pre-link args
7781
// for the target specification, so keep that in sync.
7882
#[cfg(not(test))]

library/std/src/sys/unix/stdio.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::io::{self, IoSlice, IoSliceMut};
1+
use crate::io::{self, BufferMode, IoSlice, IoSliceMut};
22
use crate::mem::ManuallyDrop;
33
use crate::os::unix::io::{AsFd, BorrowedFd, FromRawFd};
44
use crate::sys::fd::FileDesc;
@@ -139,3 +139,9 @@ impl<'a> AsFd for io::StderrLock<'a> {
139139
unsafe { BorrowedFd::borrow_raw(libc::STDERR_FILENO) }
140140
}
141141
}
142+
143+
/// Get the default stdout buffermode. In the future, this should be determined
144+
/// via `isatty` or some similar check on stdout
145+
pub fn default_stdout_buffer_mode() -> BufferMode {
146+
BufferMode::Line
147+
}

library/std/src/sys/unsupported/stdio.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::io;
1+
use crate::io::{self, BufferMode};
22

33
pub struct Stdin;
44
pub struct Stdout;
@@ -57,3 +57,7 @@ pub fn is_ebadf(_err: &io::Error) -> bool {
5757
pub fn panic_output() -> Option<Vec<u8>> {
5858
None
5959
}
60+
61+
pub fn default_stdout_buffer_mode() -> BufferMode {
62+
BufferMode::Line
63+
}

library/std/src/sys/wasi/stdio.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![deny(unsafe_op_in_unsafe_fn)]
22

33
use super::fd::WasiFd;
4-
use crate::io::{self, IoSlice, IoSliceMut};
4+
use crate::io::{self, BufferMode, IoSlice, IoSliceMut};
55
use crate::mem::ManuallyDrop;
66
use crate::os::raw;
77
use crate::os::wasi::io::{AsRawFd, FromRawFd};
@@ -110,3 +110,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
110110
pub fn panic_output() -> Option<impl io::Write> {
111111
Some(Stderr::new())
112112
}
113+
114+
pub fn default_stdout_buffer_mode() -> BufferMode {
115+
BufferMode::Line
116+
}

library/std/src/sys/windows/stdio.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use crate::char::decode_utf16;
44
use crate::cmp;
5-
use crate::io;
5+
use crate::io::{self, BufferMode};
66
use crate::os::windows::io::{FromRawHandle, IntoRawHandle};
77
use crate::ptr;
88
use crate::str;
@@ -420,3 +420,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
420420
pub fn panic_output() -> Option<impl io::Write> {
421421
Some(Stderr::new())
422422
}
423+
424+
pub fn default_stdout_buffer_mode() -> BufferMode {
425+
BufferMode::Line
426+
}

0 commit comments

Comments
 (0)