fix(client): send an Error::Cancel if a queued request is dropped
Adds `Error::Cancel` variant.
This commit is contained in:
@@ -57,6 +57,12 @@ impl Cancel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Canceled {
|
||||||
|
pub fn cancel(&self) {
|
||||||
|
self.inner.is_canceled.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Future for Canceled {
|
impl Future for Canceled {
|
||||||
type Item = ();
|
type Item = ();
|
||||||
type Error = Never;
|
type Error = Never;
|
||||||
@@ -87,7 +93,7 @@ impl Future for Canceled {
|
|||||||
|
|
||||||
impl Drop for Canceled {
|
impl Drop for Canceled {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.inner.is_canceled.store(true, Ordering::SeqCst);
|
self.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
extern crate pretty_env_logger;
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
use futures::{future, Future};
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
#[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")]
|
#[cfg(feature = "nightly")]
|
||||||
#[bench]
|
#[bench]
|
||||||
fn cancelable_queue_throughput(b: &mut test::Bencher) {
|
fn cancelable_queue_throughput(b: &mut test::Bencher) {
|
||||||
|
|
||||||
let (tx, mut rx) = super::channel::<i32, ()>();
|
let (tx, mut rx) = super::channel::<i32, ()>();
|
||||||
|
|
||||||
b.iter(move || {
|
b.iter(move || {
|
||||||
|
|||||||
48
src/error.rs
48
src/error.rs
@@ -17,6 +17,7 @@ use self::Error::{
|
|||||||
Status,
|
Status,
|
||||||
Timeout,
|
Timeout,
|
||||||
Upgrade,
|
Upgrade,
|
||||||
|
Cancel,
|
||||||
Io,
|
Io,
|
||||||
TooLarge,
|
TooLarge,
|
||||||
Incomplete,
|
Incomplete,
|
||||||
@@ -47,6 +48,8 @@ pub enum Error {
|
|||||||
Timeout,
|
Timeout,
|
||||||
/// A protocol upgrade was encountered, but not yet supported in hyper.
|
/// A protocol upgrade was encountered, but not yet supported in hyper.
|
||||||
Upgrade,
|
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.
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
/// Parsing a field as string failed
|
/// Parsing a field as string failed
|
||||||
@@ -56,6 +59,45 @@ pub enum Error {
|
|||||||
__Nonexhaustive(Void)
|
__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)]
|
#[doc(hidden)]
|
||||||
pub struct Void(());
|
pub struct Void(());
|
||||||
|
|
||||||
@@ -87,6 +129,7 @@ impl StdError for Error {
|
|||||||
Incomplete => "message is incomplete",
|
Incomplete => "message is incomplete",
|
||||||
Timeout => "timeout",
|
Timeout => "timeout",
|
||||||
Upgrade => "unsupported protocol upgrade",
|
Upgrade => "unsupported protocol upgrade",
|
||||||
|
Cancel(ref e) => e.description(),
|
||||||
Uri(ref e) => e.description(),
|
Uri(ref e) => e.description(),
|
||||||
Io(ref e) => e.description(),
|
Io(ref e) => e.description(),
|
||||||
Utf8(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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
|||||||
Reference in New Issue
Block a user