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