Skip to content

Commit 0306d73

Browse files
committed
io: fix the hang-forever issue of Mock when the write buf is empty
Signed-off-by: ADD-SP <[email protected]>
1 parent eb99e47 commit 0306d73

File tree

3 files changed

+95
-5
lines changed

3 files changed

+95
-5
lines changed

tokio-test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ futures-core = "0.3.0"
2424
[dev-dependencies]
2525
tokio = { version = "1.2.0", path = "../tokio", features = ["full"] }
2626
futures-util = "0.3.0"
27+
futures-test = "0.3.5"
2728

2829
[package.metadata.docs.rs]
2930
all-features = true

tokio-test/src/io.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use tokio::sync::mpsc;
2323
use tokio::time::{self, Duration, Instant, Sleep};
2424
use tokio_stream::wrappers::UnboundedReceiverStream;
2525

26+
use core::panic;
2627
use futures_core::Stream;
2728
use std::collections::VecDeque;
2829
use std::fmt;
@@ -235,7 +236,9 @@ impl Inner {
235236
let err = Arc::try_unwrap(err).expect("There are no other references.");
236237
Err(err)
237238
}
238-
Some(_) => {
239+
Some(&mut Action::Write(_))
240+
| Some(&mut Action::WriteError(_))
241+
| Some(&mut Action::Wait(_)) => {
239242
// Either waiting or expecting a write
240243
Err(io::ErrorKind::WouldBlock.into())
241244
}
@@ -280,10 +283,8 @@ impl Inner {
280283
Action::Wait(..) | Action::WriteError(..) => {
281284
break;
282285
}
283-
_ => {}
286+
Action::Read(_) | Action::ReadError(_) => (),
284287
}
285-
286-
// TODO: remove write
287288
}
288289

289290
Ok(ret)
@@ -441,6 +442,7 @@ impl AsyncWrite for Mock {
441442
panic!("unexpected WouldBlock {}", self.pmsg());
442443
}
443444
}
445+
Ok(0) if buf.is_empty() => return Poll::Ready(Ok(0)),
444446
Ok(0) => {
445447
// TODO: Is this correct?
446448
if !self.inner.actions.is_empty() {
@@ -494,7 +496,7 @@ impl Drop for Mock {
494496
"There is still data left to write. {}",
495497
self.pmsg()
496498
),
497-
_ => (),
499+
Action::ReadError(_) | Action::WriteError(_) | Action::Wait(_) => (),
498500
});
499501
}
500502
}

tokio-test/tests/io.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#![warn(rust_2018_idioms)]
22

3+
use futures_test::task::{noop_context, panic_waker};
4+
use futures_util::pin_mut;
5+
use std::future::Future;
36
use std::io;
7+
use std::task::{Context, Poll};
8+
use tokio::io::AsyncWrite;
49
use tokio::io::{AsyncReadExt, AsyncWriteExt};
510
use tokio::time::{Duration, Instant};
11+
use tokio_test::assert_pending;
612
use tokio_test::io::Builder;
713

814
#[tokio::test]
@@ -170,3 +176,84 @@ async fn multiple_wait() {
170176
start.elapsed().as_millis()
171177
);
172178
}
179+
180+
// No matter which usecase, it doesn't make sense for a read
181+
// hang forever. However, currently, if there is no sequenced read
182+
// action, it will hang forever.
183+
//
184+
// Since we want be aware of the fixing of this bug,
185+
// no matter intentionally or unintentionally,
186+
// we add this test to catch the behavior change.
187+
//
188+
// It looks like fixing it is not hard, but not sure the downstream
189+
// impact, which might be a breaking change due to the
190+
// `Mock::inner::read_wait` field, so we keep it as is for now.
191+
//
192+
// TODO: fix this bug
193+
#[test]
194+
fn should_hang_forever_on_read_but_no_sequenced_read_action() {
195+
let mut mock = Builder::new()
196+
.write_error(io::Error::new(io::ErrorKind::Other, "cruel"))
197+
.build();
198+
199+
let mut buf = [0; 1];
200+
let read_exact_fut = mock.read(&mut buf);
201+
pin_mut!(read_exact_fut);
202+
assert_pending!(read_exact_fut.poll(&mut Context::from_waker(&panic_waker())));
203+
}
204+
205+
// The `Mock` is expected to always panic if there is an unconsumed error action,
206+
// rather than silently ignoring it. However,
207+
// currently it only panics on unconsumed read/write actions,
208+
// not on error actions. Fixing this requires a breaking change.
209+
//
210+
// This test verifies that it does not panic yet,
211+
// to prevent accidentally introducing the breaking change prematurely.
212+
//
213+
// TODO: fix this bug in the next major release
214+
#[test]
215+
fn do_not_panic_unconsumed_error() {
216+
let _mock = Builder::new()
217+
.read_error(io::Error::new(io::ErrorKind::Other, "cruel"))
218+
.build();
219+
}
220+
221+
// The `Mock` must never panic, even if cloned multiple times.
222+
// However, at present, cloning the builder under certain
223+
// conditions causes a panic.
224+
//
225+
// Fixing this would require making `Mock` non-`Clone`,
226+
// which is a breaking change.
227+
//
228+
// Since we want be aware of the fixing of this bug,
229+
// no matter intentionally or unintentionally,
230+
// we add this test to catch the behavior change.
231+
//
232+
// TODO: fix this bug in the next major release
233+
#[tokio::test]
234+
#[should_panic = "There are no other references.: Custom { kind: Other, error: \"cruel\" }"]
235+
async fn panic_if_clone_the_build_with_error_action() {
236+
let mut builder = Builder::new();
237+
builder.write_error(io::Error::new(io::ErrorKind::Other, "cruel"));
238+
let mut builder2 = builder.clone();
239+
240+
let mut mock = builder.build();
241+
let _mock2 = builder2.build();
242+
243+
// this write_all will panic due to unwrapping the error from `Arc`
244+
mock.write_all(b"hello").await.unwrap();
245+
unreachable!();
246+
}
247+
248+
#[tokio::test]
249+
async fn should_not_hang_forever_on_zero_length_write() {
250+
let mock = Builder::new().write(b"write").build();
251+
pin_mut!(mock);
252+
match mock.as_mut().poll_write(&mut noop_context(), &[0u8; 0]) {
253+
// drain the remaining write action to avoid panic at drop of the `mock`
254+
Poll::Ready(Ok(0)) => mock.write_all(b"write").await.unwrap(),
255+
Poll::Ready(Ok(n)) => panic!("expected to write 0 bytes, wrote {n} bytes instead"),
256+
Poll::Ready(Err(e)) => panic!("expected to write 0 bytes, got error {e} instead"),
257+
Poll::Pending => panic!("expected to write 0 bytes immediately, but pending instead"),
258+
}
259+
}

0 commit comments

Comments
 (0)