fix(client): prevent a checkout loop of pooled connections that aren't ready yet
This commit is contained in:
		| @@ -107,7 +107,7 @@ impl<T, U> Sender<T, U> { | |||||||
|  |  | ||||||
| impl<T, U> UnboundedSender<T, U> { | impl<T, U> UnboundedSender<T, U> { | ||||||
|     pub fn is_ready(&self) -> bool { |     pub fn is_ready(&self) -> bool { | ||||||
|         self.giver.is_wanting() |         !self.giver.is_canceled() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn is_closed(&self) -> bool { |     pub fn is_closed(&self) -> bool { | ||||||
|   | |||||||
| @@ -318,6 +318,15 @@ where C: Connect + Sync + 'static, | |||||||
|                                     Ok(()) |                                     Ok(()) | ||||||
|                                 }) |                                 }) | ||||||
|                             ); |                             ); | ||||||
|  |                         } else { | ||||||
|  |                             // There's no body to delay, but the connection isn't | ||||||
|  |                             // ready yet. Only re-insert when it's ready | ||||||
|  |                             executor.execute( | ||||||
|  |                                 future::poll_fn(move || { | ||||||
|  |                                     pooled.poll_ready() | ||||||
|  |                                 }) | ||||||
|  |                                 .then(|_| Ok(())) | ||||||
|  |                             ); | ||||||
|                         } |                         } | ||||||
|                         Ok(res) |                         Ok(res) | ||||||
|                     }); |                     }); | ||||||
| @@ -441,6 +450,13 @@ impl<B> PoolClient<B> { | |||||||
|             PoolTx::Http2(ref tx) => tx.is_ready(), |             PoolTx::Http2(ref tx) => tx.is_ready(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn is_closed(&self) -> bool { | ||||||
|  |         match self.tx { | ||||||
|  |             PoolTx::Http1(ref tx) => tx.is_closed(), | ||||||
|  |             PoolTx::Http2(ref tx) => tx.is_closed(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<B: Payload + 'static> PoolClient<B> { | impl<B: Payload + 'static> PoolClient<B> { | ||||||
| @@ -460,10 +476,10 @@ impl<B> Poolable for PoolClient<B> | |||||||
| where | where | ||||||
|     B: 'static, |     B: 'static, | ||||||
| { | { | ||||||
|     fn is_closed(&self) -> bool { |     fn is_open(&self) -> bool { | ||||||
|         match self.tx { |         match self.tx { | ||||||
|             PoolTx::Http1(ref tx) => tx.is_closed(), |             PoolTx::Http1(ref tx) => tx.is_ready(), | ||||||
|             PoolTx::Http2(ref tx) => tx.is_closed(), |             PoolTx::Http2(ref tx) => tx.is_ready(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ pub(super) struct Pool<T> { | |||||||
| // | // | ||||||
| // See https://github.com/hyperium/hyper/issues/1429 | // See https://github.com/hyperium/hyper/issues/1429 | ||||||
| pub(super) trait Poolable: Sized { | pub(super) trait Poolable: Sized { | ||||||
|     fn is_closed(&self) -> bool; |     fn is_open(&self) -> bool; | ||||||
|     /// Reserve this connection. |     /// Reserve this connection. | ||||||
|     /// |     /// | ||||||
|     /// Allows for HTTP/2 to return a shared reservation. |     /// Allows for HTTP/2 to return a shared reservation. | ||||||
| @@ -236,7 +236,7 @@ impl<'a, T: Poolable + 'a> IdlePopper<'a, T> { | |||||||
|         while let Some(entry) = self.list.pop() { |         while let Some(entry) = self.list.pop() { | ||||||
|             // If the connection has been closed, or is older than our idle |             // If the connection has been closed, or is older than our idle | ||||||
|             // timeout, simply drop it and keep looking... |             // timeout, simply drop it and keep looking... | ||||||
|             if entry.value.is_closed() { |             if !entry.value.is_open() { | ||||||
|                 trace!("removing closed connection for {:?}", self.key); |                 trace!("removing closed connection for {:?}", self.key); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| @@ -377,7 +377,7 @@ impl<T: Poolable> PoolInner<T> { | |||||||
|  |  | ||||||
|         self.idle.retain(|key, values| { |         self.idle.retain(|key, values| { | ||||||
|             values.retain(|entry| { |             values.retain(|entry| { | ||||||
|                 if entry.value.is_closed() { |                 if !entry.value.is_open() { | ||||||
|                     trace!("idle interval evicting closed for {:?}", key); |                     trace!("idle interval evicting closed for {:?}", key); | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
| @@ -475,7 +475,7 @@ impl<T: Poolable> DerefMut for Pooled<T> { | |||||||
| impl<T: Poolable> Drop for Pooled<T> { | impl<T: Poolable> Drop for Pooled<T> { | ||||||
|     fn drop(&mut self) { |     fn drop(&mut self) { | ||||||
|         if let Some(value) = self.value.take() { |         if let Some(value) = self.value.take() { | ||||||
|             if value.is_closed() { |             if !value.is_open() { | ||||||
|                 // If we *already* know the connection is done here, |                 // If we *already* know the connection is done here, | ||||||
|                 // it shouldn't be re-inserted back into the pool. |                 // it shouldn't be re-inserted back into the pool. | ||||||
|                 return; |                 return; | ||||||
| @@ -519,7 +519,7 @@ impl<T: Poolable> Checkout<T> { | |||||||
|         if let Some(mut rx) = self.waiter.take() { |         if let Some(mut rx) = self.waiter.take() { | ||||||
|             match rx.poll() { |             match rx.poll() { | ||||||
|                 Ok(Async::Ready(value)) => { |                 Ok(Async::Ready(value)) => { | ||||||
|                     if !value.is_closed() { |                     if value.is_open() { | ||||||
|                         Ok(Async::Ready(Some(self.pool.reuse(&self.key, value)))) |                         Ok(Async::Ready(Some(self.pool.reuse(&self.key, value)))) | ||||||
|                     } else { |                     } else { | ||||||
|                         Err(::Error::new_canceled(Some(CANCELED))) |                         Err(::Error::new_canceled(Some(CANCELED))) | ||||||
| @@ -662,8 +662,8 @@ mod tests { | |||||||
|     struct Uniq<T>(T); |     struct Uniq<T>(T); | ||||||
|  |  | ||||||
|     impl<T> Poolable for Uniq<T> { |     impl<T> Poolable for Uniq<T> { | ||||||
|         fn is_closed(&self) -> bool { |         fn is_open(&self) -> bool { | ||||||
|             false |             true | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         fn reserve(self) -> Reservation<Self> { |         fn reserve(self) -> Reservation<Self> { | ||||||
| @@ -671,21 +671,6 @@ mod tests { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* |  | ||||||
|     #[derive(Debug, PartialEq, Eq, Clone, Copy)] |  | ||||||
|     struct Share<T>(T); |  | ||||||
|  |  | ||||||
|     impl<T> Poolable for Share<T> { |  | ||||||
|         fn is_closed(&self) -> bool { |  | ||||||
|             false |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fn reserve(self) -> Reservation<Self> { |  | ||||||
|             Reservation::Shared(self.clone(), self) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     */ |  | ||||||
|  |  | ||||||
|     fn c<T: Poolable>(key: Key) -> Connecting<T> { |     fn c<T: Poolable>(key: Key) -> Connecting<T> { | ||||||
|         Connecting { |         Connecting { | ||||||
|             key, |             key, | ||||||
| @@ -817,8 +802,8 @@ mod tests { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     impl Poolable for CanClose { |     impl Poolable for CanClose { | ||||||
|         fn is_closed(&self) -> bool { |         fn is_open(&self) -> bool { | ||||||
|             self.closed |             !self.closed | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         fn reserve(self) -> Reservation<Self> { |         fn reserve(self) -> Reservation<Self> { | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ fn conn_reset_after_write() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // sleep to allow some time for the connection to return to the pool |     // sleep to allow some time for the connection to return to the pool | ||||||
|     thread::sleep(Duration::from_secs(1)); |     thread::sleep(Duration::from_millis(50)); | ||||||
|  |  | ||||||
|     let req = Request::builder() |     let req = Request::builder() | ||||||
|         .uri("http://mock.local/a") |         .uri("http://mock.local/a") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user