Skip to content

fix(load): release completion handle when the response future resolves to Err#873

Open
ameyypawar wants to merge 2 commits into
tower-rs:masterfrom
ameyypawar:fix/load-completion-err-handle
Open

fix(load): release completion handle when the response future resolves to Err#873
ameyypawar wants to merge 2 commits into
tower-rs:masterfrom
ameyypawar:fix/load-completion-err-handle

Conversation

@ameyypawar

Copy link
Copy Markdown

Summary

Fixes #858.

TrackCompletionFuture::poll only took the load-tracking Handle on the success path:

let rsp = ready!(this.future.poll(cx))?;   // `?` returns early on Err
let h = this.handle.take().expect("handle");

When the inner response future resolved to Err, ? returned before handle.take() ran, so the Handle stayed alive until the outer future object was dropped rather than when the request actually completed. Both PendingRequests and PeakEwma track load through this Handle's Drop, so a failed request kept counting as pending — and, for PeakEwma, delayed its RTT sample — until drop time. Callers that hold onto completed error futures (logging, retries, batching) can therefore make an endpoint look busier or slower than it really is.

Fix

Match on the polled result and release the handle on both branches:

match ready!(this.future.poll(cx)) {
    Ok(rsp) => {
        let h = this.handle.take().expect("handle");
        Poll::Ready(Ok(this.completion.track_completion(h, rsp)))
    }
    Err(err) => {
        drop(this.handle.take().expect("handle"));
        Poll::Ready(Err(err))
    }
}

The success path is unchanged (the Ok arm is identical to the previous two lines); only the error path's timing changes — the handle is now released as soon as the response future resolves, regardless of outcome. This single change fixes both PendingRequests and PeakEwma, since both route their futures through TrackCompletionFuture.

Dropping the handle (rather than forwarding it) is the only option on the error path: TrackCompletion::track_completion(&self, handle, value: V) requires the success value V, which doesn't exist on Err. A handle can therefore only ever be forwarded into a successful response; on error, the RAII Drop is the intended completion signal.

One thing worth a maintainer's eye: as before, PeakEwma records an RTT sample for failed requests when the handle drops — this PR only corrects when that happens (at response completion instead of at future-drop). If folding failed-request latencies into the EWMA is undesirable, that's a separate change.

Tests

Adds a PendingRequests regression test (failed_response_completes_immediately) asserting that load returns to Count(0) as soon as the failed response future resolves, while the future is still held. It fails before this change and passes after.

`TrackCompletionFuture::poll` only took the load-tracking handle on the
success path (`ready!(..)?` returned early on error), so a failed response
kept its handle alive until the future object was dropped. That made
`PendingRequests` overcount failed requests as still pending and delayed
`PeakEwma`'s RTT sample until drop time.

Match on the polled result and take/drop the handle on both branches, so
both load metrics are updated as soon as the response future resolves,
regardless of outcome. Add a `PendingRequests` regression test covering the
error path.
Verify that a failed response stops contributing to PeakEwma load as soon as
the response future resolves, rather than only when the future is dropped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TrackCompletionFuture retains the load-tracking handle on Err until the future is dropped

1 participant