Change:
Try piping this program's stdout to . After the first 10 lines, will exit. The pipe is now broken, so the next write should fail, and we should get Instead, the Rust program keeps running indefinitely. GDB says is "received" (but I guess ignored) by the process every time it tries to write. Feels like a race condition: if you remove the or just change it to be short, panics as expected. tested on Linux,
I believe what's happening here is in because the underlying primitive is buffered (e.g. ) but then when is next called we and return that the bytes were written. If you let the program run long enough the buffer fills up and the error gets propagated through a call to I believe. Seems kinda weird to me to ignore that error, we should probably do something like this instead: That is, propagate an error in flush if we wrote the entire line.
Well... maybe not. So apparently this behavior changed in to fix cc thoughts?
Interesting. The new code doesn't satisfy the "write-xor-error guarantee" either, though. It does up to three operations on the underlying , so of course the first can succeed and others can then fail. For example, could write successfully all the way through, then fail trying to write the rest. Maybe the guarantee should be weakened to a reasonable-effort sort of thing. It's important in some systems-y cases where the user knows the type of the writer (e.g. a raw being used as a transaction log). It doesn't seem valuable more generally, and I'd expect third-party impls of to break it more often than not.
This also interacts with the which indicates That's probably just an erroneous clause targeted at, for example, file descriptors. With buffered readers and writers (like a line writer) I think we fundamentally just can't uphold that guarantee. Nominating for discussion as I think we'll want to reword that documentation.
In some cases, 's caller is counting on the fact that an error means no bytes were written. For example, if an operation returns , the caller needs to know where to pick up writing again. An abstract interface can't reasonably guarantee that any specific downstream source has received and acknowledged the data. On Unix, even if you do a system call and it succeeds, the kernel might still be buffering the data, so you don't know if it's made it to disk or not. You have to do an to find out whether it really made it. But an abstract interface can and should be expected to let its user keep track of where it needs to continue its work, in the case of an intermittent error condition like .
That clause is actually pretty important. Otherwise, you can't be sure of the state of a writer after it returns an error. For example, the following could would write "abc" twice if interrupted: When I wrote that patch, I was thinking that the next write after a failed flush would error out but obviously didn't think it though. So, a simple fix would be to record that a flush didn't happen and try to flush before writing anything else (i.e., on the next call to write). Unfortunately, this does mean that writing a newline wouldn't guarantee a flush but I don't think there's much we can do about that at this point. I believe a error was discussed in the past but was rejected as it would make larger. Alternatively, as we haven't actually written anything to the underlying file yet, could just "undo" the write (either by reaching into or re-implementing ).
yeah it's definitely true that it's useful to have this guarantee, and it may just not be possible to relax the guarantee. yeah on second though retrying the flush on the next sounds like it'd be a good idea (at least for now). I think that we'll need to update no matter what b/c the successfully written bytes are papered over with an error I think if the second write fails, right?
My point is to flush before doing anything else if the previous flush-on-newline failed. Only then do we write iff that flush succeeds. If the flush fails, return an error without writing anything (no papering over written bytes). However, the previously written bytes will not have been flushed so the documentation needs to be updated to say that we try to flush on newline but that flush may fail silently in some cases (and that the user should call to be absolutely sure).
yeah that sounds good to me!
Since and are friends, I think it's possible to do this so that the first error is not ignored.
Yeah that also seems reasonable to me, we could have reach into internals if necessary.
Right, is just an implementation detail of , so if BW is not giving LW the information it needs, unless there's some more fundamental reason LW can't be implemented as specified, LW's implementation should just change.
Oh, it turns out my hunch was wrong. inherently needs to do and then on the underlying writer. If the succeeds, we have to ignore errors from .
I've opened a PR as

Discussion:
#[stable(feature = "rust1", since = "1.0.0")] pub struct LineWriter<W: Write> { inner: BufWriter<W>, <ins> need_flush: bool, </ins> } impl<W: Write> LineWriter<W> {

/// ``` #[stable(feature = "rust1", since = "1.0.0")] pub fn with_capacity(cap: usize, inner: W) -> LineWriter<W> { <del> LineWriter { inner: BufWriter::with_capacity(cap, inner) } </del> <ins> LineWriter { inner: BufWriter::with_capacity(cap, inner), need_flush: false, } </ins> } /// Gets a reference to the underlying writer.

#[stable(feature = "rust1", since = "1.0.0")] pub fn into_inner(self) -> Result<W, IntoInnerError<LineWriter<W>>> { self.inner.into_inner().map_err(|IntoInnerError(buf, e)| { <del> IntoInnerError(LineWriter { inner: buf }, e) </del> <ins> IntoInnerError(LineWriter { inner: buf, need_flush: false, }, e) </ins> }) } }

#[stable(feature = "rust1", since = "1.0.0")] impl<W: Write> Write for LineWriter<W> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> { <del> match memchr::memrchr(b'n', buf) { Some(i) => { let n = self.inner.write(&buf[..i + 1])?; if n != i + 1 || self.inner.flush().is_err() { // Do not return errors on partial writes. return Ok(n); } self.inner.write(&buf[i + 1..]).map(|i| n + i) } None => self.inner.write(buf), </del> <ins> if self.need_flush { self.flush()?; } // Find the last newline character in the buffer provided. If found then // 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. let i = match memchr::memrchr(b'n', buf) { Some(i) => i, 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), </ins> } } <del> fn flush(&mut self) -> io::Result<()> { self.inner.flush() } </del> <ins> fn flush(&mut self) -> io::Result<()> { self.inner.flush()?; self.need_flush = false; Ok(()) } </ins> } #[stable(feature = "rust1", since = "1.0.0")]

BufWriter::new(io::sink()) }); } <ins> 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"anbn"); 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"anbna").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) } </ins> }