Skip to content

Commit 017ed5a

Browse files
committed
Improvements to LineWriter::write_all
`LineWriter::write_all` now only emits a single write when writing a newline when there's already buffered data.
1 parent 3aa233d commit 017ed5a

File tree

1 file changed

+79
-26
lines changed

1 file changed

+79
-26
lines changed

library/std/src/io/buffered.rs

+79-26
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,8 @@ impl<W: Write> BufWriter<W> {
527527
/// errors from this method.
528528
fn flush_buf(&mut self) -> io::Result<()> {
529529
/// Helper struct to ensure the buffer is updated after all the writes
530-
/// are complete
530+
/// are complete. It tracks the number of written bytes and drains them
531+
/// all from the front of the buffer when dropped.
531532
struct BufGuard<'a> {
532533
buffer: &'a mut Vec<u8>,
533534
written: usize,
@@ -942,12 +943,13 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> {
942943
/// success, the remaining data will be buffered.
943944
///
944945
/// Because this function attempts to send completed lines to the underlying
945-
/// writer, it will also flush the existing buffer if it contains any
946-
/// newlines, even if the incoming data does not contain any newlines.
946+
/// writer, it will also flush the existing buffer if it ends with a
947+
/// newline, even if the incoming data does not contain any newlines.
947948
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
948949
let newline_idx = match memchr::memrchr(b'\n', buf) {
949950
// If there are no new newlines (that is, if this write is less than
950-
// one line), just do a regular buffered write
951+
// one line), just do a regular buffered write (which may flush if
952+
// we exceed the inner buffer's size)
951953
None => {
952954
self.flush_if_completed_line()?;
953955
return self.buffer.write(buf);
@@ -957,7 +959,11 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> {
957959
Some(newline_idx) => newline_idx + 1,
958960
};
959961

960-
// Flush existing content to prepare for our write
962+
// Flush existing content to prepare for our write. We have to do this
963+
// before attempting to write `buf` in order to maintain consistency;
964+
// if we add `buf` to the buffer then try to flush it all at once,
965+
// we're obligated to return Ok(), which would mean supressing any
966+
// errors that occur during flush.
961967
self.buffer.flush_buf()?;
962968

963969
// This is what we're going to try to write directly to the inner
@@ -1121,32 +1127,33 @@ impl<'a, W: Write> Write for LineWriterShim<'a, W> {
11211127
/// writer, it will also flush the existing buffer if it contains any
11221128
/// newlines, even if the incoming data does not contain any newlines.
11231129
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
1124-
let newline_idx = match memchr::memrchr(b'\n', buf) {
1130+
match memchr::memrchr(b'\n', buf) {
11251131
// If there are no new newlines (that is, if this write is less than
1126-
// one line), just do a regular buffered write
1132+
// one line), just do a regular buffered write (which may flush if
1133+
// we exceed the inner buffer's size)
11271134
None => {
11281135
self.flush_if_completed_line()?;
1129-
return self.buffer.write_all(buf);
1136+
self.buffer.write_all(buf)
11301137
}
1131-
// Otherwise, arrange for the lines to be written directly to the
1132-
// inner writer.
1133-
Some(newline_idx) => newline_idx,
1134-
};
1135-
1136-
// Flush existing content to prepare for our write
1137-
self.buffer.flush_buf()?;
1138+
Some(newline_idx) => {
1139+
let (lines, tail) = buf.split_at(newline_idx + 1);
11381140

1139-
// This is what we're going to try to write directly to the inner
1140-
// writer. The rest will be buffered, if nothing goes wrong.
1141-
let (lines, tail) = buf.split_at(newline_idx + 1);
1142-
1143-
// Write `lines` directly to the inner writer, bypassing the buffer.
1144-
self.inner_mut().write_all(lines)?;
1141+
if self.buffered().is_empty() {
1142+
self.inner_mut().write_all(lines)?;
1143+
} else {
1144+
// If there is any buffered data, we add the incoming lines
1145+
// to that buffer before flushing, which saves us at lease
1146+
// one write call. We can't really do this with `write`,
1147+
// since we can't do this *and* not suppress errors *and*
1148+
// report a consistent state to the caller in a return
1149+
// value, but here in write_all it's fine.
1150+
self.buffer.write_all(lines)?;
1151+
self.buffer.flush_buf()?;
1152+
}
11451153

1146-
// Now that the write has succeeded, buffer the rest with
1147-
// BufWriter::write_all. This will buffer as much as possible, but
1148-
// continue flushing as necessary if our tail is huge.
1149-
self.buffer.write_all(tail)
1154+
self.buffer.write_all(tail)
1155+
}
1156+
}
11501157
}
11511158
}
11521159

@@ -1401,7 +1408,11 @@ mod tests {
14011408
// rustfmt-on-save.
14021409
impl Read for ShortReader {
14031410
fn read(&mut self, _: &mut [u8]) -> io::Result<usize> {
1404-
if self.lengths.is_empty() { Ok(0) } else { Ok(self.lengths.remove(0)) }
1411+
if self.lengths.is_empty() {
1412+
Ok(0)
1413+
} else {
1414+
Ok(self.lengths.remove(0))
1415+
}
14051416
}
14061417
}
14071418

@@ -2260,4 +2271,46 @@ mod tests {
22602271
writer.flush().unwrap();
22612272
assert_eq!(writer.get_ref().buffer, *b"AAAAABBBBB");
22622273
}
2274+
2275+
#[derive(Debug, Clone, PartialEq, Eq)]
2276+
enum RecordedEvent {
2277+
Write(String),
2278+
Flush,
2279+
}
2280+
2281+
#[derive(Debug, Clone, Default)]
2282+
struct WriteRecorder {
2283+
pub events: Vec<RecordedEvent>,
2284+
}
2285+
2286+
impl Write for WriteRecorder {
2287+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
2288+
use crate::str::from_utf8;
2289+
2290+
self.events.push(RecordedEvent::Write(from_utf8(buf).unwrap().to_string()));
2291+
Ok(buf.len())
2292+
}
2293+
2294+
fn flush(&mut self) -> io::Result<()> {
2295+
self.events.push(RecordedEvent::Flush);
2296+
Ok(())
2297+
}
2298+
}
2299+
2300+
/// Test that a normal, formatted writeln only results in a single write
2301+
/// call to the underlying writer. A naive implementaton of
2302+
/// LineWriter::write_all results in two writes: one of the buffered data,
2303+
/// and another of the final substring in the formatted set
2304+
#[test]
2305+
fn single_formatted_write() {
2306+
let writer = WriteRecorder::default();
2307+
let mut writer = LineWriter::new(writer);
2308+
2309+
// Under a naive implementation of LineWriter, this will result in two
2310+
// writes: "hello, world" and "!\n", because write() has to flush the
2311+
// buffer before attempting to write the last "!\n". write_all shouldn't
2312+
// have this limitation.
2313+
writeln!(&mut writer, "{}, {}!", "hello", "world").unwrap();
2314+
assert_eq!(writer.get_ref().events, [RecordedEvent::Write("hello, world!\n".to_string())]);
2315+
}
22632316
}

0 commit comments

Comments
 (0)