summaryrefslogtreecommitdiffstats
path: root/library/std/src/io/buffered/linewritershim.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/std/src/io/buffered/linewritershim.rs')
-rw-r--r--library/std/src/io/buffered/linewritershim.rs276
1 files changed, 276 insertions, 0 deletions
diff --git a/library/std/src/io/buffered/linewritershim.rs b/library/std/src/io/buffered/linewritershim.rs
new file mode 100644
index 000000000..0175d2693
--- /dev/null
+++ b/library/std/src/io/buffered/linewritershim.rs
@@ -0,0 +1,276 @@
+use crate::io::{self, BufWriter, IoSlice, Write};
+use crate::sys_common::memchr;
+
+/// Private helper struct for implementing the line-buffered writing logic.
+/// This shim temporarily wraps a BufWriter, and uses its internals to
+/// implement a line-buffered writer (specifically by using the internal
+/// methods like write_to_buf and flush_buf). In this way, a more
+/// efficient abstraction can be created than one that only had access to
+/// `write` and `flush`, without needlessly duplicating a lot of the
+/// implementation details of BufWriter. This also allows existing
+/// `BufWriters` to be temporarily given line-buffering logic; this is what
+/// enables Stdout to be alternately in line-buffered or block-buffered mode.
+#[derive(Debug)]
+pub struct LineWriterShim<'a, W: Write> {
+ buffer: &'a mut BufWriter<W>,
+}
+
+impl<'a, W: Write> LineWriterShim<'a, W> {
+ pub fn new(buffer: &'a mut BufWriter<W>) -> Self {
+ Self { buffer }
+ }
+
+ /// Get a reference to the inner writer (that is, the writer
+ /// wrapped by the BufWriter).
+ fn inner(&self) -> &W {
+ self.buffer.get_ref()
+ }
+
+ /// Get a mutable reference to the inner writer (that is, the writer
+ /// wrapped by the BufWriter). Be careful with this writer, as writes to
+ /// it will bypass the buffer.
+ fn inner_mut(&mut self) -> &mut W {
+ self.buffer.get_mut()
+ }
+
+ /// Get the content currently buffered in self.buffer
+ fn buffered(&self) -> &[u8] {
+ self.buffer.buffer()
+ }
+
+ /// Flush the buffer iff the last byte is a newline (indicating that an
+ /// earlier write only succeeded partially, and we want to retry flushing
+ /// the buffered line before continuing with a subsequent write)
+ fn flush_if_completed_line(&mut self) -> io::Result<()> {
+ match self.buffered().last().copied() {
+ Some(b'\n') => self.buffer.flush_buf(),
+ _ => Ok(()),
+ }
+ }
+}
+
+impl<'a, W: Write> Write for LineWriterShim<'a, W> {
+ /// Write some data into this BufReader with line buffering. This means
+ /// that, if any newlines are present in the data, the data up to the last
+ /// newline is sent directly to the underlying writer, and data after it
+ /// is buffered. Returns the number of bytes written.
+ ///
+ /// This function operates on a "best effort basis"; in keeping with the
+ /// convention of `Write::write`, it makes at most one attempt to write
+ /// new data to the underlying writer. If that write only reports a partial
+ /// success, the remaining data will be buffered.
+ ///
+ /// Because this function attempts to send completed lines to the underlying
+ /// writer, it will also flush the existing buffer if it ends with a
+ /// newline, even if the incoming data does not contain any newlines.
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let newline_idx = match memchr::memrchr(b'\n', buf) {
+ // If there are no new newlines (that is, if this write is less than
+ // one line), just do a regular buffered write (which may flush if
+ // we exceed the inner buffer's size)
+ None => {
+ self.flush_if_completed_line()?;
+ return self.buffer.write(buf);
+ }
+ // Otherwise, arrange for the lines to be written directly to the
+ // inner writer.
+ Some(newline_idx) => newline_idx + 1,
+ };
+
+ // Flush existing content to prepare for our write. We have to do this
+ // before attempting to write `buf` in order to maintain consistency;
+ // if we add `buf` to the buffer then try to flush it all at once,
+ // we're obligated to return Ok(), which would mean suppressing any
+ // errors that occur during flush.
+ self.buffer.flush_buf()?;
+
+ // This is what we're going to try to write directly to the inner
+ // writer. The rest will be buffered, if nothing goes wrong.
+ let lines = &buf[..newline_idx];
+
+ // Write `lines` directly to the inner writer. In keeping with the
+ // `write` convention, make at most one attempt to add new (unbuffered)
+ // data. Because this write doesn't touch the BufWriter state directly,
+ // and the buffer is known to be empty, we don't need to worry about
+ // self.buffer.panicked here.
+ let flushed = self.inner_mut().write(lines)?;
+
+ // If buffer returns Ok(0), propagate that to the caller without
+ // doing additional buffering; otherwise we're just guaranteeing
+ // an "ErrorKind::WriteZero" later.
+ if flushed == 0 {
+ return Ok(0);
+ }
+
+ // Now that the write has succeeded, buffer the rest (or as much of
+ // the rest as possible). If there were any unwritten newlines, we
+ // only buffer out to the last unwritten newline that fits in the
+ // buffer; this helps prevent flushing partial lines on subsequent
+ // calls to LineWriterShim::write.
+
+ // Handle the cases in order of most-common to least-common, under
+ // the presumption that most writes succeed in totality, and that most
+ // writes are smaller than the buffer.
+ // - Is this a partial line (ie, no newlines left in the unwritten tail)
+ // - If not, does the data out to the last unwritten newline fit in
+ // the buffer?
+ // - If not, scan for the last newline that *does* fit in the buffer
+ let tail = if flushed >= newline_idx {
+ &buf[flushed..]
+ } else if newline_idx - flushed <= self.buffer.capacity() {
+ &buf[flushed..newline_idx]
+ } else {
+ let scan_area = &buf[flushed..];
+ let scan_area = &scan_area[..self.buffer.capacity()];
+ match memchr::memrchr(b'\n', scan_area) {
+ Some(newline_idx) => &scan_area[..newline_idx + 1],
+ None => scan_area,
+ }
+ };
+
+ let buffered = self.buffer.write_to_buf(tail);
+ Ok(flushed + buffered)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buffer.flush()
+ }
+
+ /// Write some vectored data into this BufReader with line buffering. This
+ /// means that, if any newlines are present in the data, the data up to
+ /// and including the buffer containing the last newline is sent directly
+ /// to the inner writer, and the data after it is buffered. Returns the
+ /// number of bytes written.
+ ///
+ /// This function operates on a "best effort basis"; in keeping with the
+ /// convention of `Write::write`, it makes at most one attempt to write
+ /// new data to the underlying writer.
+ ///
+ /// Because this function attempts to send completed lines to the underlying
+ /// writer, it will also flush the existing buffer if it contains any
+ /// newlines.
+ ///
+ /// Because sorting through an array of `IoSlice` can be a bit convoluted,
+ /// This method differs from write in the following ways:
+ ///
+ /// - It attempts to write the full content of all the buffers up to and
+ /// including the one containing the last newline. This means that it
+ /// may attempt to write a partial line, that buffer has data past the
+ /// newline.
+ /// - If the write only reports partial success, it does not attempt to
+ /// find the precise location of the written bytes and buffer the rest.
+ ///
+ /// If the underlying vector doesn't support vectored writing, we instead
+ /// simply write the first non-empty buffer with `write`. This way, we
+ /// get the benefits of more granular partial-line handling without losing
+ /// anything in efficiency
+ fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
+ // If there's no specialized behavior for write_vectored, just use
+ // write. This has the benefit of more granular partial-line handling.
+ if !self.is_write_vectored() {
+ return match bufs.iter().find(|buf| !buf.is_empty()) {
+ Some(buf) => self.write(buf),
+ None => Ok(0),
+ };
+ }
+
+ // Find the buffer containing the last newline
+ let last_newline_buf_idx = bufs
+ .iter()
+ .enumerate()
+ .rev()
+ .find_map(|(i, buf)| memchr::memchr(b'\n', buf).map(|_| i));
+
+ // If there are no new newlines (that is, if this write is less than
+ // one line), just do a regular buffered write
+ let last_newline_buf_idx = match last_newline_buf_idx {
+ // No newlines; just do a normal buffered write
+ None => {
+ self.flush_if_completed_line()?;
+ return self.buffer.write_vectored(bufs);
+ }
+ Some(i) => i,
+ };
+
+ // Flush existing content to prepare for our write
+ self.buffer.flush_buf()?;
+
+ // This is what we're going to try to write directly to the inner
+ // writer. The rest will be buffered, if nothing goes wrong.
+ let (lines, tail) = bufs.split_at(last_newline_buf_idx + 1);
+
+ // Write `lines` directly to the inner writer. In keeping with the
+ // `write` convention, make at most one attempt to add new (unbuffered)
+ // data. Because this write doesn't touch the BufWriter state directly,
+ // and the buffer is known to be empty, we don't need to worry about
+ // self.panicked here.
+ let flushed = self.inner_mut().write_vectored(lines)?;
+
+ // If inner returns Ok(0), propagate that to the caller without
+ // doing additional buffering; otherwise we're just guaranteeing
+ // an "ErrorKind::WriteZero" later.
+ if flushed == 0 {
+ return Ok(0);
+ }
+
+ // Don't try to reconstruct the exact amount written; just bail
+ // in the event of a partial write
+ let lines_len = lines.iter().map(|buf| buf.len()).sum();
+ if flushed < lines_len {
+ return Ok(flushed);
+ }
+
+ // Now that the write has succeeded, buffer the rest (or as much of the
+ // rest as possible)
+ let buffered: usize = tail
+ .iter()
+ .filter(|buf| !buf.is_empty())
+ .map(|buf| self.buffer.write_to_buf(buf))
+ .take_while(|&n| n > 0)
+ .sum();
+
+ Ok(flushed + buffered)
+ }
+
+ fn is_write_vectored(&self) -> bool {
+ self.inner().is_write_vectored()
+ }
+
+ /// Write some data into this BufReader with line buffering. This means
+ /// that, if any newlines are present in the data, the data up to the last
+ /// newline is sent directly to the underlying writer, and data after it
+ /// is buffered.
+ ///
+ /// Because this function attempts to send completed lines to the underlying
+ /// writer, it will also flush the existing buffer if it contains any
+ /// newlines, even if the incoming data does not contain any newlines.
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ match memchr::memrchr(b'\n', buf) {
+ // If there are no new newlines (that is, if this write is less than
+ // one line), just do a regular buffered write (which may flush if
+ // we exceed the inner buffer's size)
+ None => {
+ self.flush_if_completed_line()?;
+ self.buffer.write_all(buf)
+ }
+ Some(newline_idx) => {
+ let (lines, tail) = buf.split_at(newline_idx + 1);
+
+ if self.buffered().is_empty() {
+ self.inner_mut().write_all(lines)?;
+ } else {
+ // If there is any buffered data, we add the incoming lines
+ // to that buffer before flushing, which saves us at least
+ // one write call. We can't really do this with `write`,
+ // since we can't do this *and* not suppress errors *and*
+ // report a consistent state to the caller in a return
+ // value, but here in write_all it's fine.
+ self.buffer.write_all(lines)?;
+ self.buffer.flush_buf()?;
+ }
+
+ self.buffer.write_all(tail)
+ }
+ }
+ }
+}