Nobody has started this yet — be first.
Business impact
An http.Response.Body that's never closed doesn't just leak memory -- Go's net/http transport can't return the underlying TCP connection to its connection pool until the body is closed, so a leaked body means a leaked connection. Under light, ad-hoc testing (a handful of manual requests) this is invisible; under real production load, with every retried request leaking one connection per discarded attempt, this exhausts the transport's connection pool and/or file descriptor limits -- exactly the failure mode most likely to strike during a downstream outage, which is also exactly when this package's retry logic is under the most load and doing the most retrying. This is the same class of failure the project's own RoundTripper doc comment specifically calls out avoiding ("drain and close it so its connection can be reused, rather than leaking it").
Problem
Every time RoundTripper.RoundTrip starts a new attempt, it's supposed to drain and close the previous (discarded) attempt's response body before replacing it. It now only drains it.
Current behavior
For a request that gets retried N times before succeeding (or exhausting), none of the N-1 discarded intermediate response bodies are ever closed.
Expected behavior
Every discarded intermediate response body must have Close() called on it before RoundTrip moves on to the next attempt. The final response (the one actually returned to the caller) is untouched -- closing it is the caller's own responsibility, same as any other http.Response.
Steps to reproduce
// see test/task13_test.go for the full countingBody / scriptedTransport setup rt := retry.NewRoundTripper(mockTransportReturning(503, 503, 200), policy) resp, _ := rt.RoundTrip(req) fmt.Println(closedCount) // got: 0, want: 2 (the two discarded 503 responses)
Why this matters
Resource leaks in error/retry paths are notoriously easy to introduce (the "happy path" -- one successful attempt, no discarding -- never exercises this code at all) and notoriously hard to notice without either a dedicated test or a production incident with connection pool exhaustion symptoms.
Suggested approach
Find where a previous lastResp is discarded inside RoundTrip's retry closure (right before starting a new attempt) -- it does an io.Copy to drain the body. Compare this to what the same block's own doc comment above it says should happen.
Acceptance criteria
Verification
go test -race ./test/ -run TestTask13 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/roundtripper-body-leakFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/roundtripper-body-leakSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8080POST /v1/chargesTo run against Redis (the multi-replica-safe backend) with zero local setup:
docker compose up --build # app + redis, hot-reloading on save
Work the tickets in tasks/ (ordered easy → hard); each names one Go test in test/:
make test-race # go test -race -count=1 ./...
go test ./test/ -run TestTask01 -v # a single ticket
Level 1
Fix a bug
Read existing behaviour, correct it.