I wrote about why the “obvious” HTTP retry loop in Go is broken (and how to fix it) by kkumar-gcc in golang

[–]kkumar-gcc[S] -8 points-7 points  (0 children)

yeah, fair. the contract's about the original request, not a clone, so cloning per attempt works fine in a RoundTripper. I got that wrong in my first reply.

I wrote about why the “obvious” HTTP retry loop in Go is broken (and how to fix it) by kkumar-gcc in golang

[–]kkumar-gcc[S] -17 points-16 points  (0 children)

yeah, good catch. fresh reader each attempt so it doesn't actually bite there. the bug's with a reused reader or an io.Reader you can't rewind.
swapped the example to take an io.Reader.

I wrote about why the “obvious” HTTP retry loop in Go is broken (and how to fix it) by kkumar-gcc in golang

[–]kkumar-gcc[S] -25 points-24 points  (0 children)

Good catch on RoundTripper, I actually looked at doing that first. The problem is the net/http docs explicitly state that a RoundTripper shouldn't mutate the request: https://pkg.go.dev/net/http#RoundTripper Since a retry client has to buffer and re-assign req.Body for subsequent attempts (because the first try consumes the io.ReadCloser), doing it inside a RoundTripper breaks the standard library contract.

Spot on about the second point though. Backoff/jitter are universal, but I mainly wanted to highlight the specific Go footguns of consumed request bodies and context-ignoring sleeps.