Skip to content

refactor: common retry handling - #472

Open
alexanto wants to merge 1 commit into
refactor/error-contractfrom
refactor/retry-policy
Open

alexanto wants to merge 1 commit into
refactor/error-contractfrom
refactor/retry-policy

Conversation

@alexanto

@alexanto alexanto commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

moved retries out of the transports into one shared file in the SDK. retries should be the responsibility of the SDK imo, httpOptions retries will go away
same type of errors should be retried as before but added backoff for retries

@alexanto
alexanto force-pushed the refactor/retry-policy branch from 4c0fc66 to 83b7a9d Compare November 5, 2026 13:28
@gastonfournier gastonfournier moved this from New to In Progress in Issues and PRs Nov 6, 2026
@alexanto
alexanto requested review from ppicom and a balanced review from Copilot November 6, 2026 09:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Uncapped exponential backoff can block application startup for days, while overly broad exception retries and lost Retry-After handling introduce additional retry regressions.

3 open issues
What changed in this PR

This PR moves feature-fetch retries into a shared SDK policy used by both synchronous and asynchronous clients, leaving transports responsible for individual requests.

Changes:

  • Centralizes retry decisions and adds exponential backoff with jitter.
  • Updates both API clients to use the shared retry helpers.
  • Adds retry-policy tests, verifies single-attempt transport behavior, and documents the change.
File Description
UnleashClient/​_unleash_api_client.py Routes synchronous feature fetches through shared retries.
UnleashClient/​_transport.py Removes transport-level retry configuration.
UnleashClient/​_retry.py Introduces shared retry decisions and backoff helpers.
UnleashClient/​_async_unleash_api_client.py Replaces its retry loop with the shared async helper.
tests/​unit_tests/​test_transport.py Verifies transports send requests only once.
tests/​unit_tests/​test_retry.py Covers retry outcomes, cancellation, and backoff.
tests/​conftest.py Disables retry delays by default during tests.
CHANGELOG.md Documents shared retries and randomized backoff.

Review effort: Balanced

Comment thread UnleashClient/_retry.py
``BACKOFF_BASE * 2 ** retry``, so 0.25-0.5s, 0.5-1s, 1-2s, and clients that
failed together don't retry together.
"""
ceiling = BACKOFF_BASE * 2**retry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i removed the option to configure retry count in a later PR, because I though retry should be the SDK's job, not sth the users decide
said that, we have no way of identifying is a custom transport implementation has retry mechanism, and then it can stack up with out implementation, but i would like to trust the SDK users to not mess that up
there is a warning for that in the readme, and also if they use our RequestTransport class we do log a warning about the duplicate retries

maybe makes sense to allow the users to disable the built-in retry if they really need to, but I would wait for them to explicitly ask for it. then, I have a feeling that it might be better off in the polling options then in the transport

Comment thread UnleashClient/_retry.py
Comment on lines +47 to +48
if isinstance(outcome, Response):
return outcome.status in RETRY_STATUSES

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is actually true for the sync client, not the async, so not sure how much this was intented in the first place
so leaning towards not caring about this one @ppicom wdyt?

Comment thread UnleashClient/_retry.py
return False
if isinstance(outcome, Response):
return outcome.status in RETRY_STATUSES
return not isinstance(outcome, AlreadyClosedError)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have no way of telling a transient error apart from a permanent one, since custom transport can use any lib they want
but i don't think it is a huge problem since the worst case is 3 extra attempts now for a permanent error

might be worth adding a few non-retryable errors, i am think about adding it in a separate PR

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

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants