v0.3.32: Honor Retry-After on HTTP 429 with jittered exponential backoff #28

Merged
sysadmin merged 1 commits from fix/v0.3.32-retry-after-backoff into master 2026-07-01 21:04:34 -05:00
Owner

Closes #27.

What

gitea_auth.api_request — the single HTTP entry point for all Gitea API calls — now retries HTTP 429 (rate-limit) responses instead of failing immediately.

Behavior

  • Honor Retry-After when present and valid — both forms:
    • seconds ("120")
    • HTTP-date ("Wed, 21 Oct 2015 07:28:00 GMT", clamped to 0 if in the past)
  • Fallback to full-jitter capped exponential backoff when Retry-After is missing or invalid: random delay in [0, min(cap, base * 2**attempt)].
  • Bounded: retries capped by max_retries, delay capped by max_delay — no infinite loops.
  • Config via env with safe defaults: GITEA_MAX_RETRIES (3), GITEA_RETRY_BASE_DELAY (1.0s), GITEA_RETRY_MAX_DELAY (60.0s).
  • Unchanged: successful responses and non-429 errors behave exactly as before. New params are keyword-only with defaults → all existing positional callers unaffected.

Tests

tests/test_retry_backoff.py — 23 cases. Deterministic: sleep, random, and clock are injected; no real sleeping, randomness, or network. Covers Retry-After seconds, Retry-After HTTP-date, invalid/missing-header backoff fallback, max-retries give-up, no-infinite-loop, delay cap, and jitter bounds.

Full suite: 133 passed, 0 failures.


⚠️ Authored by me — do not self-merge. Needs review by another author.

Closes #27. ## What `gitea_auth.api_request` — the single HTTP entry point for all Gitea API calls — now retries HTTP 429 (rate-limit) responses instead of failing immediately. ## Behavior - **Honor `Retry-After`** when present and valid — both forms: - seconds (`"120"`) - HTTP-date (`"Wed, 21 Oct 2015 07:28:00 GMT"`, clamped to `0` if in the past) - **Fallback** to full-jitter capped exponential backoff when `Retry-After` is missing or invalid: random delay in `[0, min(cap, base * 2**attempt)]`. - **Bounded**: retries capped by `max_retries`, delay capped by `max_delay` — no infinite loops. - **Config** via env with safe defaults: `GITEA_MAX_RETRIES` (3), `GITEA_RETRY_BASE_DELAY` (1.0s), `GITEA_RETRY_MAX_DELAY` (60.0s). - **Unchanged**: successful responses and non-429 errors behave exactly as before. New params are keyword-only with defaults → all existing positional callers unaffected. ## Tests `tests/test_retry_backoff.py` — 23 cases. Deterministic: `sleep`, `random`, and clock are injected; no real sleeping, randomness, or network. Covers Retry-After seconds, Retry-After HTTP-date, invalid/missing-header backoff fallback, max-retries give-up, no-infinite-loop, delay cap, and jitter bounds. Full suite: **133 passed, 0 failures**. --- ⚠️ Authored by me — do **not** self-merge. Needs review by another author.
jcwalker3 added 1 commit 2026-07-01 20:29:21 -05:00
api_request now retries HTTP 429 responses instead of failing immediately:

- Parse and honor a valid Retry-After header (seconds or HTTP-date).
- Fall back to full-jitter capped exponential backoff when the header is
  missing or invalid.
- Bound retries by max_retries and delay by max_delay (env-overridable via
  GITEA_MAX_RETRIES / GITEA_RETRY_BASE_DELAY / GITEA_RETRY_MAX_DELAY) — no
  infinite loops.
- Non-429 errors and successful responses are unchanged.

Sleep, randomness, and clock are injectable so retry timing is tested
deterministically. Adds tests/test_retry_backoff.py (23 cases).

Closes #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sysadmin merged commit 20dd717b9c into master 2026-07-01 21:04:34 -05:00
sysadmin deleted branch fix/v0.3.32-retry-after-backoff 2026-07-01 21:04:34 -05:00
Sign in to join this conversation.