-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fs: Spawn new write task while buf is not empty #6330
Conversation
202745b
to
bc27f6c
Compare
Fixes: tokio-rs#6325 Signed-off-by: Xuanwo <[email protected]>
bc27f6c
to
28d88cc
Compare
The current implementation causes pub async fn into_std(mut self) -> StdFile {
self.inner.get_mut().complete_inflight().await;
Arc::try_unwrap(self.std).expect("Arc::try_unwrap failed")
} I'm still finding a way to address it. |
I'm having a little bit of difficulty understanding the solution here. Here is my understanding: A call to The problem is the code that writes the buffer into the std file: pub(crate) fn write_to<T: Write>(&mut self, wr: &mut T) -> io::Result<()> {
assert_eq!(self.pos, 0);
// `write_all` already ignores interrupts
let res = wr.write_all(&self.buf);
self.buf.clear();
res
} This function will clear the contents of the buffer regardless of whether So the returned number of bytes is correct. The error returned from flush is also correct. But the combination of these two cannot help the user to recover from the error since it's unclear how many bytes were actually written to disk. I guess a handwritten implementation of
|
It's possible that we may have to say that recovering from errors with |
I'm not entirely sure if this is the best solution. I'm open to exploring other options.
I understand your point that handling recovery from errors with The example I provided in the issue relates to a disk being full, but this situation could arise with any error occurring during writing. |
For the most part, a |
BufWrtier will return error during let mut f = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open("/tmp/test_dir/test")
.unwrap();
// Large than write size
let mut f = BufWriter::with_capacity(4 * 1024 *1024, f);
let size = 512 * 1024 + 1;
let mut bs = vec![0; size];
thread_rng().fill_bytes(&mut bs);
let n = f.write(&bs)?;
dbg!(&n)
let res = f.flush();
dbg!(&res);
let res = f.flush();
dbg!(&res); Output: [src/main.rs:61] &n = 524289
[src/main.rs:65] &res = Err(
Os {
code: 28,
kind: StorageFull,
message: "No space left on device",
},
)
[src/main.rs:69] &res = Err(
Os {
code: 28,
kind: StorageFull,
message: "No space left on device",
},
) Internally, https://doc.rust-lang.org/src/std/io/buffered/bufwriter.rs.html#180-224 |
I generally agree w/ @Darksonn's take. Tokio's behavior should more or less follow |
It sounds like the issue is that the buffer is cleared after I think that should fix the issue? |
Yep. I believe this should work. |
There is another problem: Is it necessary to submit a new write task when the user calls |
So, what I think should happen is that when an operation fails, we don't throw away the buffer. Then, we retry the operation the next time that the user flushes. Why do you need to add the |
I need the |
You should be able to add an |
Thanks, I will give it a try. |
I don't have interest in this anymore, closing... Thanks you all for the review and advice. |
Motivation
Fix #6325
tokio::fs could have data loss if flush failed.
Solution
This PR changed the
poll_flush
to rebuild the write buf is still empty.To make it possible, I store an
Arc<StdFile>
inState
. This change also remove the need to close file for most IO operations (alought it doesn't has big impact on the performance).