Repository navigation
fix: close retried responses in retry middleware - #16
Merged
Merged
Conversation
Retry kept a response it decided to retry in lastResponse and looped, and nothing closed its body. Each retried response held one TCP connection and its transport goroutines for the lifetime of the client. RoundTrip closes the body of a response dropped in favor of another attempt, for a retryable status and for a response returned together with an error. The response of the last attempt is returned to the caller open, as before. The body is closed without draining. A read in the middleware has no time bound and would block the retry when an upstream sends headers and stalls the body. The go1.27.1 transport drains on close within its own limits and reuses the connection. The go1.25.0 transport does not, so there a retried response costs a new connection, which is closed and no longer leaked. Related to #15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Retrykept a response it decided to retry inlastResponseand looped, and nothing closed its body:net/httpreleases a connection only after the body is read to EOF or closed, so each retried response held one TCP connection and its transport goroutines for the lifetime of the client. The reproducer from #15 (a server answering 502, 502, 200, five requests) ends with 11 connections still open and 35 goroutines.RoundTripnow closes the body of a response it drops in favor of another attempt, both for a retryable status and for a response returned together with an error. The response of the last attempt is returned to the caller open, as before.The body is closed without draining, which differs from the fix suggested in the issue. A read in the middleware has no time bound, so a server that sends headers and then stalls the body would block the retry forever on a client without a timeout, where today
Retrymoves on to the next attempt. Closing alone is enough to release the connection. Same reproducer after the change:The go1.27.1 transport drains on close within its own limits and reuses the connection. The go1.25.0 transport does not, so there a retried response costs a new connection, which is closed and no longer leaked.
Related to #15