fix(client): detect connection closes as pool tries to use
Currently, if the remote closes the connection at the same time that the pool selects it to use for a new request, the connection may actually hang. This fix will now more allow the keep-alive read to check the socket even when the `Conn` think it's busy. If the connection was closed before the request write happened, returns back an `Error::Cancel`, letting the user know they could safely retry it. Closes #1439
This commit is contained in:
		| @@ -85,7 +85,7 @@ impl<T, U> Drop for Receiver<T, U> { | ||||
|         // - 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())); | ||||
|             let _ = cb.send(Err(::Error::new_canceled(None))); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -243,11 +243,7 @@ where C: Connect, | ||||
|                 }, | ||||
|                 Err(_) => { | ||||
|                     error!("pooled connection was not ready, this is a hyper bug"); | ||||
|                     let err = io::Error::new( | ||||
|                         io::ErrorKind::BrokenPipe, | ||||
|                         "pool selected dead connection", | ||||
|                     ); | ||||
|                     Either::B(future::err(::Error::Io(err))) | ||||
|                     Either::B(future::err(::Error::new_canceled(None))) | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|   | ||||
							
								
								
									
										17
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -60,9 +60,9 @@ pub enum Error { | ||||
| } | ||||
|  | ||||
| impl Error { | ||||
|     pub(crate) fn new_canceled() -> Error { | ||||
|     pub(crate) fn new_canceled(cause: Option<Error>) -> Error { | ||||
|         Error::Cancel(Canceled { | ||||
|             _inner: (), | ||||
|             cause: cause.map(Box::new), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @@ -73,10 +73,9 @@ impl Error { | ||||
| /// 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. | ||||
| #[derive(Debug)] | ||||
| pub struct Canceled { | ||||
|     // maybe in the future this contains an optional value of | ||||
|     // what was canceled? | ||||
|     _inner: (), | ||||
|     cause: Option<Box<Error>>, | ||||
| } | ||||
|  | ||||
| impl Canceled { | ||||
| @@ -85,13 +84,6 @@ impl Canceled { | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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()) | ||||
| @@ -142,6 +134,7 @@ impl StdError for Error { | ||||
|             Io(ref error) => Some(error), | ||||
|             Uri(ref error) => Some(error), | ||||
|             Utf8(ref error) => Some(error), | ||||
|             Cancel(ref e) => e.cause.as_ref().map(|e| &**e as &StdError), | ||||
|             Error::__Nonexhaustive(..) =>  unreachable!(), | ||||
|             _ => None, | ||||
|         } | ||||
|   | ||||
| @@ -119,7 +119,7 @@ where I: AsyncRead + AsyncWrite, | ||||
|             } else { | ||||
|                 trace!("poll when on keep-alive"); | ||||
|                 if !T::should_read_first() { | ||||
|                     self.try_empty_read()?; | ||||
|                     self.require_empty_read()?; | ||||
|                     if self.is_read_closed() { | ||||
|                         return Ok(Async::Ready(None)); | ||||
|                     } | ||||
| @@ -281,17 +281,23 @@ where I: AsyncRead + AsyncWrite, | ||||
|     pub fn read_keep_alive(&mut self) -> Result<(), ::Error> { | ||||
|         debug_assert!(!self.can_read_head() && !self.can_read_body()); | ||||
|  | ||||
|         trace!("Conn::read_keep_alive"); | ||||
|         trace!("read_keep_alive; is_mid_message={}", self.is_mid_message()); | ||||
|  | ||||
|         if T::should_read_first() || !self.state.is_idle() { | ||||
|         if self.is_mid_message() { | ||||
|             self.maybe_park_read(); | ||||
|         } else { | ||||
|             self.try_empty_read()?; | ||||
|             self.require_empty_read()?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn is_mid_message(&self) -> bool { | ||||
|         match (&self.state.reading, &self.state.writing) { | ||||
|             (&Reading::Init, &Writing::Init) => false, | ||||
|             _ => true, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn maybe_park_read(&mut self) { | ||||
|         if !self.io.is_read_blocked() { | ||||
|             // the Io object is ready to read, which means it will never alert | ||||
| @@ -312,40 +318,68 @@ where I: AsyncRead + AsyncWrite, | ||||
|     // | ||||
|     // This should only be called for Clients wanting to enter the idle | ||||
|     // state. | ||||
|     fn try_empty_read(&mut self) -> io::Result<()> { | ||||
|     fn require_empty_read(&mut self) -> io::Result<()> { | ||||
|         assert!(!self.can_read_head() && !self.can_read_body()); | ||||
|  | ||||
|         if !self.io.read_buf().is_empty() { | ||||
|             debug!("received an unexpected {} bytes", self.io.read_buf().len()); | ||||
|             Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected bytes after message ended")) | ||||
|         } else { | ||||
|              match self.io.read_from_io() { | ||||
|                 Ok(Async::Ready(0)) => { | ||||
|                     trace!("try_empty_read; found EOF on connection: {:?}", self.state); | ||||
|                     let must_error = self.should_error_on_eof(); | ||||
|                     // order is important: must_error needs state BEFORE close_read | ||||
|                     self.state.close_read(); | ||||
|                     if must_error { | ||||
|                         Err(io::Error::new(io::ErrorKind::UnexpectedEof, "unexpected EOF waiting for response")) | ||||
|                     } else { | ||||
|                         Ok(()) | ||||
|                     } | ||||
|                 }, | ||||
|                 Ok(Async::Ready(n)) => { | ||||
|                     debug!("received {} bytes on an idle connection", n); | ||||
|                     Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected bytes after message ended")) | ||||
|                 }, | ||||
|                 Ok(Async::NotReady) => { | ||||
|             match self.try_io_read()? { | ||||
|                 Async::Ready(0) => { | ||||
|                     // case handled in try_io_read | ||||
|                     Ok(()) | ||||
|                 }, | ||||
|                 Async::Ready(n) => { | ||||
|                     debug!("received {} bytes on an idle connection", n); | ||||
|                     let desc = if self.state.is_idle() { | ||||
|                         "unexpected bytes after message ended" | ||||
|                     } else { | ||||
|                         "unexpected bytes before writing message" | ||||
|                     }; | ||||
|                     Err(io::Error::new(io::ErrorKind::InvalidData, desc)) | ||||
|                 }, | ||||
|                 Async::NotReady => { | ||||
|                     Ok(()) | ||||
|                 }, | ||||
|                 Err(e) => { | ||||
|                     self.state.close(); | ||||
|                     Err(e) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn try_io_read(&mut self) -> Poll<usize, io::Error> { | ||||
|          match self.io.read_from_io() { | ||||
|             Ok(Async::Ready(0)) => { | ||||
|                 trace!("try_io_read; found EOF on connection: {:?}", self.state); | ||||
|                 let must_error = self.should_error_on_eof(); | ||||
|                 let ret = if must_error { | ||||
|                     let desc = if self.is_mid_message() { | ||||
|                         "unexpected EOF waiting for response" | ||||
|                     } else { | ||||
|                         "unexpected EOF before writing message" | ||||
|                     }; | ||||
|                     Err(io::Error::new(io::ErrorKind::UnexpectedEof, desc)) | ||||
|                 } else { | ||||
|                     Ok(Async::Ready(0)) | ||||
|                 }; | ||||
|  | ||||
|                 // order is important: must_error needs state BEFORE close_read | ||||
|                 self.state.close_read(); | ||||
|                 ret | ||||
|             }, | ||||
|             Ok(Async::Ready(n)) => { | ||||
|                 Ok(Async::Ready(n)) | ||||
|             }, | ||||
|             Ok(Async::NotReady) => { | ||||
|                 Ok(Async::NotReady) | ||||
|             }, | ||||
|             Err(e) => { | ||||
|                 self.state.close(); | ||||
|                 Err(e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     fn maybe_notify(&mut self) { | ||||
|         // its possible that we returned NotReady from poll() without having | ||||
|         // exhausted the underlying Io. We would have done this when we | ||||
|   | ||||
| @@ -401,7 +401,9 @@ where | ||||
|                     let _ = cb.send(Err(err)); | ||||
|                     Ok(()) | ||||
|                 } else if let Ok(Async::Ready(Some((_, cb)))) = self.rx.poll() { | ||||
|                     let _ = cb.send(Err(err)); | ||||
|                     // in this case, the message was never even started, so it's safe to tell | ||||
|                     // the user that the request was completely canceled | ||||
|                     let _ = cb.send(Err(::Error::new_canceled(Some(err)))); | ||||
|                     Ok(()) | ||||
|                 } else { | ||||
|                     Err(err) | ||||
| @@ -431,13 +433,14 @@ where | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     extern crate pretty_env_logger; | ||||
|  | ||||
|     use super::*; | ||||
|     use mock::AsyncIo; | ||||
|     use proto::ClientTransaction; | ||||
|  | ||||
|     #[test] | ||||
|     fn client_read_response_before_writing_request() { | ||||
|         extern crate pretty_env_logger; | ||||
|     fn client_read_bytes_before_writing_request() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         ::futures::lazy(|| { | ||||
|             let io = AsyncIo::new_buf(b"HTTP/1.1 200 OK\r\n\r\n".to_vec(), 100); | ||||
| @@ -452,11 +455,16 @@ mod tests { | ||||
|             }; | ||||
|             let res_rx = tx.send((req, None::<::Body>)).unwrap(); | ||||
|  | ||||
|             dispatcher.poll().expect("dispatcher poll 1"); | ||||
|             dispatcher.poll().expect("dispatcher poll 2"); | ||||
|             let _res = res_rx.wait() | ||||
|             let a1 = dispatcher.poll().expect("error should be sent on channel"); | ||||
|             assert!(a1.is_ready(), "dispatcher should be closed"); | ||||
|             let err = res_rx.wait() | ||||
|                 .expect("callback poll") | ||||
|                 .expect("callback response"); | ||||
|                 .expect_err("callback response"); | ||||
|  | ||||
|             match err { | ||||
|                 ::Error::Cancel(_) => (), | ||||
|                 other => panic!("expected Cancel(_), got {:?}", other), | ||||
|             } | ||||
|             Ok::<(), ()>(()) | ||||
|         }).wait().unwrap(); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user