mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-08 00:00:13 +02:00
fs: restore File internal state when op fails (#8291)
This commit is contained in:
+56
-14
@@ -418,7 +418,14 @@ impl File {
|
|||||||
|
|
||||||
let (op, buf) = match inner.state {
|
let (op, buf) = match inner.state {
|
||||||
State::Idle(_) => unreachable!(),
|
State::Idle(_) => unreachable!(),
|
||||||
State::Busy(ref mut rx) => rx.await?,
|
State::Busy(ref mut rx) => {
|
||||||
|
let res = rx.await;
|
||||||
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
res?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inner.state = State::Idle(Some(buf));
|
inner.state = State::Idle(Some(buf));
|
||||||
@@ -621,7 +628,12 @@ impl AsyncRead for File {
|
|||||||
inner.state = State::Busy(Inner::poll_read_inner(std, buf, max_buf_size)?);
|
inner.state = State::Busy(Inner::poll_read_inner(std, buf, max_buf_size)?);
|
||||||
}
|
}
|
||||||
State::Busy(ref mut rx) => {
|
State::Busy(ref mut rx) => {
|
||||||
let (op, mut buf) = ready!(Pin::new(rx).poll(cx))?;
|
let res = ready!(Pin::new(rx).poll(cx));
|
||||||
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
let (op, mut buf) = res?;
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Operation::Read(Ok(_)) => {
|
Operation::Read(Ok(_)) => {
|
||||||
@@ -701,7 +713,12 @@ impl AsyncSeek for File {
|
|||||||
match inner.state {
|
match inner.state {
|
||||||
State::Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
State::Idle(_) => return Poll::Ready(Ok(inner.pos)),
|
||||||
State::Busy(ref mut rx) => {
|
State::Busy(ref mut rx) => {
|
||||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
let res = ready!(Pin::new(rx).poll(cx));
|
||||||
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
let (op, buf) = res?;
|
||||||
inner.state = State::Idle(Some(buf));
|
inner.state = State::Idle(Some(buf));
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
@@ -752,7 +769,7 @@ impl AsyncWrite for File {
|
|||||||
let n = buf.copy_from(src, me.max_buf_size);
|
let n = buf.copy_from(src, me.max_buf_size);
|
||||||
let std = me.std.clone();
|
let std = me.std.clone();
|
||||||
|
|
||||||
let blocking_task_join_handle = spawn_mandatory_blocking(move || {
|
let res = spawn_mandatory_blocking(move || {
|
||||||
let res = if let Some(seek) = seek {
|
let res = if let Some(seek) = seek {
|
||||||
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
||||||
} else {
|
} else {
|
||||||
@@ -761,16 +778,25 @@ impl AsyncWrite for File {
|
|||||||
|
|
||||||
(Operation::Write(res), buf)
|
(Operation::Write(res), buf)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "background task failed"));
|
||||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
|
||||||
})?;
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
let blocking_task_join_handle = res?;
|
||||||
|
|
||||||
inner.state = State::Busy(blocking_task_join_handle);
|
inner.state = State::Busy(blocking_task_join_handle);
|
||||||
|
|
||||||
return Poll::Ready(Ok(n));
|
return Poll::Ready(Ok(n));
|
||||||
}
|
}
|
||||||
State::Busy(ref mut rx) => {
|
State::Busy(ref mut rx) => {
|
||||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
let res = ready!(Pin::new(rx).poll(cx));
|
||||||
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
let (op, buf) = res?;
|
||||||
inner.state = State::Idle(Some(buf));
|
inner.state = State::Idle(Some(buf));
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
@@ -823,7 +849,7 @@ impl AsyncWrite for File {
|
|||||||
let n = buf.copy_from_bufs(bufs, me.max_buf_size);
|
let n = buf.copy_from_bufs(bufs, me.max_buf_size);
|
||||||
let std = me.std.clone();
|
let std = me.std.clone();
|
||||||
|
|
||||||
let blocking_task_join_handle = spawn_mandatory_blocking(move || {
|
let res = spawn_mandatory_blocking(move || {
|
||||||
let res = if let Some(seek) = seek {
|
let res = if let Some(seek) = seek {
|
||||||
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
(&*std).seek(seek).and_then(|_| buf.write_to(&mut &*std))
|
||||||
} else {
|
} else {
|
||||||
@@ -832,16 +858,25 @@ impl AsyncWrite for File {
|
|||||||
|
|
||||||
(Operation::Write(res), buf)
|
(Operation::Write(res), buf)
|
||||||
})
|
})
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "background task failed"));
|
||||||
io::Error::new(io::ErrorKind::Other, "background task failed")
|
|
||||||
})?;
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
let blocking_task_join_handle = res?;
|
||||||
|
|
||||||
inner.state = State::Busy(blocking_task_join_handle);
|
inner.state = State::Busy(blocking_task_join_handle);
|
||||||
|
|
||||||
return Poll::Ready(Ok(n));
|
return Poll::Ready(Ok(n));
|
||||||
}
|
}
|
||||||
State::Busy(ref mut rx) => {
|
State::Busy(ref mut rx) => {
|
||||||
let (op, buf) = ready!(Pin::new(rx).poll(cx))?;
|
let res = ready!(Pin::new(rx).poll(cx));
|
||||||
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
inner.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
let (op, buf) = res?;
|
||||||
inner.state = State::Idle(Some(buf));
|
inner.state = State::Idle(Some(buf));
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
@@ -1108,7 +1143,14 @@ impl Inner {
|
|||||||
|
|
||||||
let (op, buf) = match self.state {
|
let (op, buf) = match self.state {
|
||||||
State::Idle(_) => return Poll::Ready(Ok(())),
|
State::Idle(_) => return Poll::Ready(Ok(())),
|
||||||
State::Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?,
|
State::Busy(ref mut rx) => {
|
||||||
|
let res = ready!(Pin::new(rx).poll(cx));
|
||||||
|
if res.is_err() {
|
||||||
|
// Restore a valid Idle state before returning the error.
|
||||||
|
self.state = State::Idle(Some(Buf::with_capacity(0)));
|
||||||
|
}
|
||||||
|
res?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// The buffer is not used here
|
// The buffer is not used here
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#![warn(rust_2018_idioms)]
|
||||||
|
#![cfg(feature = "full")]
|
||||||
|
|
||||||
|
//! A `tokio::fs::File` op whose backing blocking task fails (cancelled/panicked
|
||||||
|
//! -> `JoinError`, or fails to spawn) must leave the File in a valid state and
|
||||||
|
//! return an `io::Error`, not poison it so a later op panics.
|
||||||
|
//!
|
||||||
|
//! We run each op on a runtime whose blocking pool is already shut down, that way
|
||||||
|
//! the task is cancelled on spawn. A poison panics here and fails the test.
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::io::SeekFrom::Start;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::task::Context;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tokio::fs::File;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
|
||||||
|
use tokio::runtime::{Builder, Handle};
|
||||||
|
|
||||||
|
fn poll_once<F: Future>(fut: F) {
|
||||||
|
let mut fut = Box::pin(fut);
|
||||||
|
let _ = fut
|
||||||
|
.as_mut()
|
||||||
|
.poll(&mut Context::from_waker(futures::task::noop_waker_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handle to a runtime whose blocking pool has already been shut down.
|
||||||
|
fn dead_pool_handle() -> Handle {
|
||||||
|
let rt = Builder::new_multi_thread().enable_all().build().unwrap();
|
||||||
|
let h = rt.handle().clone();
|
||||||
|
rt.shutdown_timeout(Duration::from_secs(0));
|
||||||
|
h
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run `ops` in order on a fresh dead-pool File.
|
||||||
|
fn run(path: &Path, h: &Handle, ops: &[fn(&mut File)]) {
|
||||||
|
let _enter = h.enter();
|
||||||
|
let opts = std::fs::File::options()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path)
|
||||||
|
.unwrap();
|
||||||
|
let mut file = File::from_std(opts);
|
||||||
|
for &op in ops {
|
||||||
|
op(&mut file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn file_ops_survive_join_error() {
|
||||||
|
let path = std::env::temp_dir().join(format!("tokio_join_error_{}", std::process::id()));
|
||||||
|
std::fs::write(&path, vec![b'x'; 4096]).unwrap();
|
||||||
|
let h = dead_pool_handle();
|
||||||
|
|
||||||
|
let read: fn(&mut File) = |f| poll_once(f.read(&mut [0u8; 8]));
|
||||||
|
let seek: fn(&mut File) = |f| poll_once(f.seek(Start(0)));
|
||||||
|
let write: fn(&mut File) = |f| poll_once(f.write(b"z"));
|
||||||
|
let flush: fn(&mut File) = |f| poll_once(f.flush());
|
||||||
|
let set_len: fn(&mut File) = |f| poll_once(f.set_len(0));
|
||||||
|
|
||||||
|
// Each op fails, however none may panic.
|
||||||
|
run(&path, &h, &[read, read]);
|
||||||
|
run(&path, &h, &[seek, seek]);
|
||||||
|
run(&path, &h, &[set_len, set_len]);
|
||||||
|
run(&path, &h, &[write, write]);
|
||||||
|
run(&path, &h, &[read, flush]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user