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 { | ||||
|     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(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 || { | ||||
|   | ||||
							
								
								
									
										48
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user