Skip to content

middleware: encode fractional Retry-After delays conservatively - #1206

Open
sergioperezcheco wants to merge 1 commit into
go-chi:masterfrom
sergioperezcheco:fix/retry-after-duration-20261005-c
Open

sergioperezcheco wants to merge 1 commit into
go-chi:masterfrom
sergioperezcheco:fix/retry-after-duration-20261005-c

Conversation

@sergioperezcheco

Copy link
Copy Markdown

Problem

ThrottleWithOpts truncates the duration returned by RetryAfterFn when encoding Retry-After. A 500ms delay is sent as 0, and a 1.5s delay as 1, asking clients to retry before the callback's delay has elapsed. Negative durations can produce a negative delay-seconds value.

Change

Encode positive durations using integer division and a remainder, rounding fractional seconds up. Clamp negative durations to 0. This avoids floating-point conversion, platform-dependent int conversion, and adding to a duration before division, so the duration limits are handled without overflow. Zero and whole-second delays keep their existing values; a nil callback still omits the header.

RFC 9110 §10.2.3 defines delay-seconds as a non-negative decimal integer. Rounding up is a conservative encoding choice, not a rounding rule mandated by the RFC.

The new regression tests exercise the public middleware through HTTP requests and handler calls. They cover capacity rejection, cancellation, backlog timeout, duration boundaries, and a nil callback, and verify that accepted HTTP requests still succeed without Retry-After.

This is a duration-encoding fix, not a rewrite of the existing throttle tests or a claim to fix #608. #474 introduced the callback API; #1194 and #1150 separately improve the existing tests. This change leaves middleware/throttle_test.go untouched.

Validation

On Go 1.26.4 / macOS arm64:

  • The exact regression tests fail against unchanged production code; zero, whole-second, and nil-callback controls pass.
  • go test -p 1 -timeout 60s ./middleware -run 'TestThrottle(RetryAfter.*Duration|NoRetryAfterCallback)' -count=10 -v passes.
  • go test -p 1 -race -count=1 -timeout 120s ./... passes in independent parent verification. The new worktree uses the same base commit and byte-identical production/test files; this full race run was reused, not repeated.
  • Fresh worktree: go test -p 1 -timeout 60s ./middleware -run TestThrottle -count=1 and go vet -p 1 ./middleware pass.
  • Parent go vet -p 1 ./..., gofmt, goimports, git diff --check, and incremental staticcheck pass.

The Linux/Windows and other Go-version CI matrix has not been run locally.

Implemented with AI assistance through Hermes Agent, with independent parent regression and full-module race verification. The positive-fraction rounding and negative-duration clamp are proposed behavior changes, not previously approved maintainer policy.

Signed-off-by: sergioperezcheco <checo520@outlook.com>

This branch has not been deployed

No deployments
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.

Refactor throttle middleware tests as prone to intermittent failure

1 participant