fix(client): send an Error::Cancel if a queued request is dropped

Adds `Error::Cancel` variant.
This commit is contained in:
Sean McArthur
2018-02-07 13:12:33 -08:00
parent a821a366f1
commit 88f01793be
3 changed files with 104 additions and 5 deletions

View File

@@ -57,6 +57,12 @@ impl Cancel {
}
}
impl Canceled {
pub fn cancel(&self) {
self.inner.is_canceled.store(true, Ordering::SeqCst);
}
}
impl Future for Canceled {
type Item = ();
type Error = Never;
@@ -87,7 +93,7 @@ impl Future for Canceled {
impl Drop for Canceled {
fn drop(&mut self) {
self.inner.is_canceled.store(true, Ordering::SeqCst);
self.cancel();
}
}

View File

@@ -69,21 +69,66 @@ impl<T, U> Stream for Receiver<T, U> {
}
}
//TODO: Drop for Receiver should consume inner
impl<T, U> Drop for Receiver<T, U> {
fn drop(&mut self) {
self.canceled.cancel();
self.inner.close();
// This poll() is safe to call in `Drop`, because we've
// called, `close`, which promises that no new messages
// will arrive, and thus, once we reach the end, we won't
// see a `NotReady` (and try to park), but a Ready(None).
//
// All other variants:
// - Ready(None): the end. we want to stop looping
// - NotReady: unreachable
// - Err: unreachable
while let Ok(Async::Ready(Some((_val, cb)))) = self.inner.poll() {
// maybe in future, we pass the value along with the error?
let _ = cb.send(Err(::Error::new_canceled()));
}
}
}
#[cfg(test)]
mod tests {
extern crate pretty_env_logger;
#[cfg(feature = "nightly")]
extern crate test;
use futures::{future, Future};
#[cfg(feature = "nightly")]
use futures::{Future, Stream};
use futures::{Stream};
#[test]
fn drop_receiver_sends_cancel_errors() {
let _ = pretty_env_logger::try_init();
future::lazy(|| {
#[derive(Debug)]
struct Custom(i32);
let (tx, rx) = super::channel::<Custom, ()>();
let promise = tx.send(Custom(43)).unwrap();
drop(rx);
promise.then(|fulfilled| {
let res = fulfilled.expect("fulfilled");
match res.unwrap_err() {
::Error::Cancel(_) => (),
e => panic!("expected Error::Cancel(_), found {:?}", e),
}
Ok::<(), ()>(())
})
}).wait().unwrap();
}
#[cfg(feature = "nightly")]
#[bench]
fn cancelable_queue_throughput(b: &mut test::Bencher) {
let (tx, mut rx) = super::channel::<i32, ()>();
b.iter(move || {

View File

@@ -17,6 +17,7 @@ use self::Error::{
Status,
Timeout,
Upgrade,
Cancel,
Io,
TooLarge,
Incomplete,
@@ -47,6 +48,8 @@ pub enum Error {
Timeout,
/// A protocol upgrade was encountered, but not yet supported in hyper.
Upgrade,
/// A pending item was dropped before ever being processed.
Cancel(Canceled),
/// An `io::Error` that occurred while trying to read or write to a network stream.
Io(IoError),
/// Parsing a field as string failed
@@ -56,6 +59,45 @@ pub enum Error {
__Nonexhaustive(Void)
}
impl Error {
pub(crate) fn new_canceled() -> Error {
Error::Cancel(Canceled {
_inner: (),
})
}
}
/// A pending item was dropped before ever being processed.
///
/// For example, a `Request` could be queued in the `Client`, *just*
/// as the related connection gets closed by the remote. In that case,
/// when the connection drops, the pending response future will be
/// fulfilled with this error, signaling the `Request` was never started.
pub struct Canceled {
// maybe in the future this contains an optional value of
// what was canceled?
_inner: (),
}
impl Canceled {
fn description(&self) -> &str {
"an operation was canceled internally before starting"
}
}
impl fmt::Debug for Canceled {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Canceled")
.finish()
}
}
impl fmt::Display for Canceled {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.pad(self.description())
}
}
#[doc(hidden)]
pub struct Void(());
@@ -87,6 +129,7 @@ impl StdError for Error {
Incomplete => "message is incomplete",
Timeout => "timeout",
Upgrade => "unsupported protocol upgrade",
Cancel(ref e) => e.description(),
Uri(ref e) => e.description(),
Io(ref e) => e.description(),
Utf8(ref e) => e.description(),
@@ -143,6 +186,11 @@ impl From<httparse::Error> for Error {
}
}
#[doc(hidden)]
trait AssertSendSync: Send + Sync + 'static {}
#[doc(hidden)]
impl AssertSendSync for Error {}
#[cfg(test)]
mod tests {
use std::error::Error as StdError;