Auto merge of #38062 - alexcrichton:fix-line-writer, r=brson
std: Fix partial writes in LineWriter Previously the `LineWriter` could successfully write some bytes but then fail to report that it has done so. Additionally, an erroneous flush after a successful write was permanently ignored. This commit fixes these two issues by (a) maintaining a `need_flush` flag to indicate whether a flush should be the first operation in `LineWriter::write` and (b) avoiding returning an error once some bytes have been successfully written. Closes #37807
This commit is contained in:
@@ -652,6 +652,7 @@ impl<W> fmt::Display for IntoInnerError<W> {
|
|||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub struct LineWriter<W: Write> {
|
pub struct LineWriter<W: Write> {
|
||||||
inner: BufWriter<W>,
|
inner: BufWriter<W>,
|
||||||
|
need_flush: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> LineWriter<W> {
|
impl<W: Write> LineWriter<W> {
|
||||||
@@ -692,7 +693,10 @@ impl<W: Write> LineWriter<W> {
|
|||||||
/// ```
|
/// ```
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub fn with_capacity(cap: usize, inner: W) -> LineWriter<W> {
|
pub fn with_capacity(cap: usize, inner: W) -> LineWriter<W> {
|
||||||
LineWriter { inner: BufWriter::with_capacity(cap, inner) }
|
LineWriter {
|
||||||
|
inner: BufWriter::with_capacity(cap, inner),
|
||||||
|
need_flush: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a reference to the underlying writer.
|
/// Gets a reference to the underlying writer.
|
||||||
@@ -759,7 +763,10 @@ impl<W: Write> LineWriter<W> {
|
|||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub fn into_inner(self) -> Result<W, IntoInnerError<LineWriter<W>>> {
|
pub fn into_inner(self) -> Result<W, IntoInnerError<LineWriter<W>>> {
|
||||||
self.inner.into_inner().map_err(|IntoInnerError(buf, e)| {
|
self.inner.into_inner().map_err(|IntoInnerError(buf, e)| {
|
||||||
IntoInnerError(LineWriter { inner: buf }, e)
|
IntoInnerError(LineWriter {
|
||||||
|
inner: buf,
|
||||||
|
need_flush: false,
|
||||||
|
}, e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -767,20 +774,46 @@ impl<W: Write> LineWriter<W> {
|
|||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
impl<W: Write> Write for LineWriter<W> {
|
impl<W: Write> Write for LineWriter<W> {
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
match memchr::memrchr(b'\n', buf) {
|
if self.need_flush {
|
||||||
Some(i) => {
|
self.flush()?;
|
||||||
let n = self.inner.write(&buf[..i + 1])?;
|
}
|
||||||
if n != i + 1 || self.inner.flush().is_err() {
|
|
||||||
// Do not return errors on partial writes.
|
// Find the last newline character in the buffer provided. If found then
|
||||||
return Ok(n);
|
// we're going to write all the data up to that point and then flush,
|
||||||
}
|
// otherewise we just write the whole block to the underlying writer.
|
||||||
self.inner.write(&buf[i + 1..]).map(|i| n + i)
|
let i = match memchr::memrchr(b'\n', buf) {
|
||||||
}
|
Some(i) => i,
|
||||||
None => self.inner.write(buf),
|
None => return self.inner.write(buf),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Ok, we're going to write a partial amount of the data given first
|
||||||
|
// followed by flushing the newline. After we've successfully written
|
||||||
|
// some data then we *must* report that we wrote that data, so future
|
||||||
|
// errors are ignored. We set our internal `need_flush` flag, though, in
|
||||||
|
// case flushing fails and we need to try it first next time.
|
||||||
|
let n = self.inner.write(&buf[..i + 1])?;
|
||||||
|
self.need_flush = true;
|
||||||
|
if self.flush().is_err() || n != i + 1 {
|
||||||
|
return Ok(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we successfully wrote `i + 1` bytes and flushed it out,
|
||||||
|
// meaning that the entire line is now flushed out on the screen. While
|
||||||
|
// we can attempt to finish writing the rest of the data provided.
|
||||||
|
// Remember though that we ignore errors here as we've successfully
|
||||||
|
// written data, so we need to report that.
|
||||||
|
match self.inner.write(&buf[i + 1..]) {
|
||||||
|
Ok(i) => Ok(n + i),
|
||||||
|
Err(_) => Ok(n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> io::Result<()> { self.inner.flush() }
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.inner.flush()?;
|
||||||
|
self.need_flush = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
@@ -1153,4 +1186,44 @@ mod tests {
|
|||||||
BufWriter::new(io::sink())
|
BufWriter::new(io::sink())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AcceptOneThenFail {
|
||||||
|
written: bool,
|
||||||
|
flushed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for AcceptOneThenFail {
|
||||||
|
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||||
|
if !self.written {
|
||||||
|
assert_eq!(data, b"a\nb\n");
|
||||||
|
self.written = true;
|
||||||
|
Ok(data.len())
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::NotFound, "test"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
assert!(self.written);
|
||||||
|
assert!(!self.flushed);
|
||||||
|
self.flushed = true;
|
||||||
|
Err(io::Error::new(io::ErrorKind::Other, "test"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn erroneous_flush_retried() {
|
||||||
|
let a = AcceptOneThenFail {
|
||||||
|
written: false,
|
||||||
|
flushed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut l = LineWriter::new(a);
|
||||||
|
assert_eq!(l.write(b"a\nb\na").unwrap(), 4);
|
||||||
|
assert!(l.get_ref().written);
|
||||||
|
assert!(l.get_ref().flushed);
|
||||||
|
l.get_mut().flushed = false;
|
||||||
|
|
||||||
|
assert_eq!(l.write(b"a").unwrap_err().kind(), io::ErrorKind::Other)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user