Skip to content

core: RetryPolicy total sleep budget (maxTotalDelay) and builder - #78

Merged
omahjoub merged 1 commit into
mainfrom
retry-policy
Aug 29, 2026
Merged

core: RetryPolicy total sleep budget (maxTotalDelay) and builder#78
omahjoub merged 1 commit into
mainfrom
retry-policy

Conversation

@omahjoub

Copy link
Copy Markdown
Owner

Summary

  • RetryPolicy bounded each sleep (maxDelay, also the Retry-After ceiling) and the attempt count, but nothing bounded their sum: raise maxDelay to bridge a per-minute window and maxAttempts to five, and one call can hold the caller's thread for minutes of sleeping. ADR-027 (plan decision D3) adds maxTotalDelay — default 1 min, the worst case the other defaults already allowed, so behaviour at the defaults is unchanged — checked before every sleep: a sleep that would exceed the budget is never started, retrying ends and the exception surfaces with its hint preserved, mirroring the ADR-025 ceiling. Exactly-reached budgets are honoured.
  • Breaking (pre-1.0, ADR-019): the canonical constructor gains a fourth Duration after maxDelay (seven components). Migration: RetryPolicy.builder() (new — starts from defaults(), one setter per knob, the path that survives future knobs), or insert Duration.ofMinutes(1) as the fourth argument. No compatibility constructor, by decision: it would keep the positional trap public beside the builder meant to end it. Zero external consumers today.
  • Starter: fanar.retry.max-total-delay (default 1m); fanarRetryPolicy is built through the builder, so the four knobs validate together and a max-delay raised above the budget fails the context at startup with the policy's own message. ADR-014 / ADR-020 amended; ARCHITECTURE, API_SKETCH §10–11, sample application.yml, PROJECT_STATE and CHANGELOG updated.

Type of change

  • Bug fix
  • New feature / capability
  • New module
  • Refactoring (no behavior change)
  • Breaking change
  • Documentation / tooling / CI only

Test plan

  • mvn verify passes locally
  • Live tests pass against the real Fanar API (if applicable, with FANAR_API_KEY set)
  • GraalVM --self-test passes (if the SDK's reflective surface changed)

Reviewer checklist

  • [ x] Public API changes are intentional, documented in javadoc, and an ADR was added/updated if the design changed
  • Which *IntegrationTest proves the behaviour this PR claims? — FanarClientRetryIntegrationTest.retryAfterHintsBeyondTheTotalBudgetEndRetrying (public builder → chain → scripted server: two 429s with Retry-After: 1 under a 1 s ceiling and 1 s budget — first slept, second refused, hint preserved, fanar.retry_count=1, no further request); loop semantics in RetryInterceptorTest (totalDelayBudget*, retryAfterHintBeyondTheRemainingBudget*); starter in FanarAutoConfigurationTest (retryKnobsAreValidatedTogetherAtStartup, knob default + override)
  • Breaking changes carry @Deprecated(since, forRemoval = true) and a migration path in the PR description
  • BOM updated if modules were added or removed
  • No third-party types leak into qa.fanar.core public API
  • If a non-obvious project decision was made, capture it in memory/ so future contributors find it

@omahjoub
omahjoub merged commit 62ea2ff into main Aug 29, 2026
5 checks passed
@omahjoub
omahjoub deleted the retry-policy branch August 29, 2026 20:03
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.

1 participant