Skip to content

Commit

Permalink
fix: erroneous retries on a failed request to a newly opened socket (#…
Browse files Browse the repository at this point in the history
…150)

The legacy pool client will in certain circumstances try other connections if it it notices an error just as it's trying to send a request. There is also some code that prevents retrying forever, such as if it is a new connection, since it likely won't ever work then.

However, there was a bug that this fixes, where if a new connection was successfully created, but _then_ errored when trying to send the request, it would consider retrying that. This could end up in a loop if the server accepts and then errors all connections.

The fix is to re-introduce tracking whether this connection has been successfully used once all the way through, "reused", and if it hasn't, abort any retrying.
  • Loading branch information
ahl authored Sep 23, 2024
1 parent 2639193 commit d3e9699
Showing 1 changed file with 12 additions and 25 deletions.
37 changes: 12 additions & 25 deletions src/client/legacy/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ macro_rules! e {
type PoolKey = (http::uri::Scheme, http::uri::Authority);

enum TrySendError<B> {
Retryable { error: Error, req: Request<B> },
Retryable {
error: Error,
req: Request<B>,
connection_reused: bool,
},
Nope(Error),
}

Expand Down Expand Up @@ -244,8 +248,12 @@ where
req = match self.try_send_request(req, pool_key.clone()).await {
Ok(resp) => return Ok(resp),
Err(TrySendError::Nope(err)) => return Err(err),
Err(TrySendError::Retryable { mut req, error }) => {
if !self.config.retry_canceled_requests {
Err(TrySendError::Retryable {
mut req,
error,
connection_reused,
}) => {
if !self.config.retry_canceled_requests || !connection_reused {
// if client disabled, don't retry
// a fresh connection means we definitely can't retry
return Err(error);
Expand Down Expand Up @@ -317,6 +325,7 @@ where
Err(mut err) => {
return if let Some(req) = err.take_message() {
Err(TrySendError::Retryable {
connection_reused: pooled.is_reused(),
error: e!(Canceled, err.into_error())
.with_connect_info(pooled.conn_info.clone()),
req,
Expand All @@ -329,8 +338,6 @@ where
}
}
};
//.send_request_retryable(req)
//.map_err(ClientError::map_with_reused(pooled.is_reused()));

// If the Connector included 'extra' info, add to Response...
if let Some(extra) = &pooled.conn_info.extra {
Expand Down Expand Up @@ -803,26 +810,6 @@ impl<B: Body + 'static> PoolClient<B> {
PoolTx::Http2(ref mut tx) => tx.try_send_request(req),
};
}

/*
//TODO: can we re-introduce this somehow? Or must people use tower::retry?
fn send_request_retryable(
&mut self,
req: Request<B>,
) -> impl Future<Output = Result<Response<hyper::body::Incoming>, (Error, Option<Request<B>>)>>
where
B: Send,
{
match self.tx {
#[cfg(not(feature = "http2"))]
PoolTx::Http1(ref mut tx) => tx.send_request_retryable(req),
#[cfg(feature = "http1")]
PoolTx::Http1(ref mut tx) => Either::Left(tx.send_request_retryable(req)),
#[cfg(feature = "http2")]
PoolTx::Http2(ref mut tx) => Either::Right(tx.send_request_retryable(req)),
}
}
*/
}

impl<B> pool::Poolable for PoolClient<B>
Expand Down

0 comments on commit d3e9699

Please sign in to comment.