Skip to content

fix(throttle): close the request body on throttle errors and log at debug - #22

Merged
adamwoolhether merged 4 commits into
mainfrom
ship/issue-21-throttle-body
Oct 6, 2026
Merged

adamwoolhether merged 4 commits into
mainfrom
ship/issue-21-throttle-body

Conversation

@adamwoolhether

@adamwoolhether adamwoolhether commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

The throttle round tripper now closes the request body when it fails a request. It also logs at Debug level instead of Info.

Changes

  • RoundTrip closes r.Body when the early context check, the limiter wait, or the post-wait context check fails. The http.RoundTripper contract requires the transport to close the body on errors. http.Client does not close it after a transport error.
  • On success, RoundTrip passes r to the next round tripper unchanged and leaves the body open.
  • The "throttle tokens exhausted" and "throttle wait complete" lines log at Debug level. A logger at the default Info level no longer writes them.
  • The throttle reads the limiter's token count only when the logger has Debug enabled.
  • Both lines log with the request context, the same context that the Debug check reads.
  • "throttle wait complete" logs only after a successful wait.

Tests

  • TestThrottleRoundTripper_ClosesBodyOnError: each of the three error returns closes the body once and sends nothing to the next round tripper.
  • TestThrottleRoundTripper_PassesRequestOn: on success, the next round tripper receives the same request with the body open.
  • TestClient_Retry_CanceledDuringThrottleWait: with WithThrottleEvery and WithRetry, a context cancelled during the wait returns an error that wraps context.Canceled, and the body closes once.
  • TestThrottleRoundTripper_LogsAtDebug: a failed wait logs no "wait complete" line, a successful wait logs one, and every throttle line is at Debug level. Its handler drops records that lack the request context.
  • TestThrottleRoundTripper_LogsRate and TestThrottleRoundTripper_LoggerTakesOneToken now read a Debug-level handler.

Follow-ups to #20

  • throttle.Config returns as a deprecated type with its released fields, RPS and Burst. feat(client): add WithThrottleEvery for fractional throttle rates #20 removed it, but every client release from v0.0.2 to v0.0.7 exports it.
  • TestClient_WithThrottleEvery_Burst checks each request result. The first two requests succeed, and the third fails its token wait with ErrWaitingFailed.
  • TestClient_WithThrottleEvery_CanceledDuringWait no longer requires ErrWaitingFailed, because a cancel that fires before the limiter wait returns ErrContextEnded.
  • A doWithin test helper fails a test when Do runs longer than a second. The burst test and both throttle cancel tests send through it, so a wait that ignores its context fails fast instead of hanging until the test timeout.

Closes #21

…ebug

- RoundTrip closes r.Body when the early context check, the limiter wait, or the post-wait context check fails.
- The RoundTripper contract requires the transport to close the body on errors. http.Client does not close it after a transport error.
- The throttle logs "tokens exhausted" and "wait complete" at Debug level.
- The throttle reads the token count only when the logger has Debug enabled.
- The throttle logs "wait complete" only after a successful wait.
@adamwoolhether adamwoolhether self-assigned this Oct 6, 2026
- The throttle logs both debug lines with DebugContext, so the records use the context that Enabled checks.
- The debug log test drops records that lack the request context, so it fails if a line logs without it.
- A comment explains why RoundTrip discards the body close error.
- The request body test spends tokens from a count field instead of a per-row branch.
- The debug log test spaces tokens 400ms apart, so a slow runner is less likely to refill the bucket early.
- Two test helper comments that restated the code are gone.
- The WithThrottleEvery change removed the exported throttle.Config type. Every client release from v0.0.2 to v0.0.7 exports it.
- Config returns with its released fields, RPS and Burst, so code that names it compiles again.
- A Deprecated notice says that nothing in the module reads Config, and it names NewRoundTripper and NewRoundTripperEvery instead.
… waits

- The burst test checks that the first two requests succeed and that the third fails its token wait with ErrWaitingFailed.
- All three burst requests share a one-minute deadline, so the limiter rejects any wait past the burst at once.
- The cancel test no longer requires ErrWaitingFailed. A cancel that fires before the request reaches the limiter returns ErrContextEnded, which is also correct.
- A doWithin helper fails a test when Do runs longer than a second, so a wait that ignores its context fails fast instead of hanging until the test timeout.
- The burst test, the WithThrottleEvery cancel test, and the throttled retry cancel test send through doWithin.
@adamwoolhether
adamwoolhether marked this pull request as ready for review October 6, 2026 05:11
@adamwoolhether
adamwoolhether merged commit 04ff191 into main Oct 6, 2026
6 checks passed
@adamwoolhether
adamwoolhether deleted the ship/issue-21-throttle-body branch October 6, 2026 05:12
adamwoolhether added a commit that referenced this pull request Oct 6, 2026
This PR removes the deprecated `throttle.Config` type that #22 restored.

## Changes
- `client/throttle` no longer exports `Config`.
- No exported API accepts `Config`, and nothing in the module reads it.
In `client/v0.0.7`, only the unexported client options used it.
- `client/v0.1.0` ships `Config` as deprecated. The next client release
removes it.
- **Breaking for code that names `throttle.Config`:** that code no
longer compiles. Such code could only build a `Config` value, because no
function takes one.
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.

Close the request body on throttle errors and quiet the throttle logs

1 participant