RHINENG-30154: close HTTP response bodies after retries - #2313
Conversation
HTTPCallRetry discarded responses without closing the body on success and retry, leaking TCP sockets from listener Candlepin calls. Co-authored-by: Cursor <cursoragent@cursor.com>
Reviewer's GuideEnsure all HTTP responses used in HTTPCallRetry and yum-update S3 requests are drained and closed to prevent leaking TCP connections, and add targeted tests to verify body draining and closure behavior in all retry paths. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
closeHTTPResponse,io.Copy(io.Discard, resp.Body)will read the entire body even for very large responses; consider either limiting the number of bytes drained (e.g., viaio.LimitReader) or only draining when you know the body is small to avoid unnecessary memory and bandwidth use. - The new
defer resp.Body.Close()ingetYumUpdatesdrops the previous error wrapping aroundresp.Body.Close; if close errors are meaningful in this context, consider at least logging them so they’re not silently ignored.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `closeHTTPResponse`, `io.Copy(io.Discard, resp.Body)` will read the entire body even for very large responses; consider either limiting the number of bytes drained (e.g., via `io.LimitReader`) or only draining when you know the body is small to avoid unnecessary memory and bandwidth use.
- The new `defer resp.Body.Close()` in `getYumUpdates` drops the previous error wrapping around `resp.Body.Close`; if close errors are meaningful in this context, consider at least logging them so they’re not silently ignored.
## Individual Comments
### Comment 1
<location path="base/utils/http_test.go" line_range="14-16" />
<code_context>
"github.com/stretchr/testify/assert"
)
+type closeTrackingBody struct {
+ io.Reader
+ closed atomic.Bool
+}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Track that the response body is actually drained, not just closed
Since `closeHTTPResponse` now drains the body with `io.Copy(io.Discard, resp.Body)`, this helper should also verify that behavior, not just that `Close` was called. Please extend it (or add another helper) to track `Read` calls or bytes read, and add a test assertion that the body is fully consumed before it is closed, to prevent regressions that skip draining.
Suggested implementation:
```golang
type closeTrackingBody struct {
rc io.ReadCloser
readBytes int64
closed atomic.Bool
}
func newCloseTrackingBody(rc io.ReadCloser) *closeTrackingBody {
return &closeTrackingBody{rc: rc}
}
func (b *closeTrackingBody) Read(p []byte) (int, error) {
n, err := b.rc.Read(p)
if n > 0 {
atomic.AddInt64(&b.readBytes, int64(n))
}
return n, err
}
func (b *closeTrackingBody) Close() error {
b.closed.Store(true)
return b.rc.Close()
}
func (b *closeTrackingBody) BytesRead() int64 {
return atomic.LoadInt64(&b.readBytes)
}
func (b *closeTrackingBody) Closed() bool {
return b.closed.Load()
}
```
To fully enforce and test drain-before-close behavior, you should also:
1. Update the test that currently uses `closeTrackingBody` (presumably the test for `closeHTTPResponse`) to:
- Wrap the body:
`bodyContent := "some body data"`
`body := newCloseTrackingBody(io.NopCloser(strings.NewReader(bodyContent)))`
- Construct the response with `Body: body`.
- Call `closeHTTPResponse(resp)`.
- Assert:
- `assert.True(t, body.Closed())`
- `assert.Equal(t, int64(len(bodyContent)), body.BytesRead())`
2. If there are multiple tests for `closeHTTPResponse`, ensure each one uses `newCloseTrackingBody` and asserts that the full body length was read before or by the time it is closed, to prevent regressions where draining is skipped.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2313 +/- ##
==========================================
+ Coverage 58.83% 59.03% +0.20%
==========================================
Files 150 150
Lines 9605 9614 +9
==========================================
+ Hits 5651 5676 +25
+ Misses 3360 3344 -16
Partials 594 594
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Unbounded io.Copy could read huge leftover bodies; cap the drain at net/http's reuse limit and log yum-update Body.Close failures. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Hi @MichaelMraka , sorry, just pushed changes to address sourcery-ai feedback Can you re-ACK when sourcery-ai completes its next review ? Thank you |
|
@sourcery-ai review |
HTTPCallRetry discarded response bodies on success and retry, so Candlepin calls from patchman-listener leaked TCP sockets until the pod restarted (RHINENG-30154). Close and drain the body on every retry path, and close S3 yum-update responses on error as well.
Test plan
go test ./base/utils/ -run TestHTTPCallRetrynode_sockstat_TCP_memon listener nodes stays flat instead of climbing ~7k pages/hourSecure Coding Practices Checklist GitHub Link
Secure Coding Checklist
Made with Cursor
Summary by Sourcery
Close HTTP response bodies consistently to prevent socket leaks in retrying API calls and S3 yum-update requests.
Bug Fixes:
Enhancements:
Tests: