Skip to content

fix: allow stream to recover from 429, 400, and 408 responses - #379

Merged
kinyoklion merged 2 commits into
mainfrom
rlamb/sdk-2824/fdv1-stream-429-recoverable
Jul 31, 2026
Merged

fix: allow stream to recover from 429, 400, and 408 responses#379
kinyoklion merged 2 commits into
mainfrom
rlamb/sdk-2824/fdv1-stream-429-recoverable

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Jul 30, 2026

Copy link
Copy Markdown
Member

The legacy streaming data source's error handler classifies every 4xx response as non-retriable (code >= 400 && code < 500), instead of using the shared rule in LDUtil.isHttpErrorRecoverable, which exempts 400, 408, and 429. As a result, a single 429 (rate limit) on a stream connection attempt permanently stops the stream: the data source logs "non-retriable", stops the EventSource, and nothing schedules a retry. The client then silently serves cached flags with no live connection until a foreground/background transition, network change, or identify call happens to rebuild the data source. 400 and 408 are mishandled the same way. (SDK-2824)

This only affects connect/reconnect-time HTTP errors; mid-stream drops are IO errors and already retry. The FDv2 synchronizers and the events sender already use the shared recoverability rule — the legacy FDv1 streaming path was the only streaming outlier, and every other LaunchDarkly SDK's streaming path treats 400/408/429 as retryable.

Change

StreamingDataSource.onError now classifies stream HTTP errors with LDUtil.isHttpErrorRecoverable(code), so 400/408/429 fall through to the existing EventSource retry path with backoff. Handling of unrecoverable codes is unchanged, including the 401 escalation to shutDown().

Testing

  • New unit tests: 429/400/408 each report a retryable failure without shutting down the sink, plus an end-to-end test where the server returns 429 then a working SSE stream and the data source reconnects and applies the put.
  • New contract tests in sdk-test-harness#395 (streaming/retry behavior for client-side SDKs), verified on an emulator: on main exactly the six recoverable-4xx cases fail (400/408/429 on initial connect and on reconnect); on this branch all of them pass, along with the full streaming category (68 tests).

Note

Medium Risk
Changes live stream reconnect behavior for specific HTTP statuses; misclassification could either retry forever on bad config or still drop connections on rate limits, but logic is shared with other SDK paths already in production.

Overview
Legacy FDv1 streaming no longer treats every 4xx as fatal on connect. StreamingDataSource.onError now uses LDUtil.isHttpErrorRecoverable(code) instead of code >= 400 && code < 500, matching FDv2 streaming, polling, and events.

400, 408, and 429 are reported as retryable LDInvalidResponseCodeFailure and stay on the EventSource backoff path instead of stopping the stream and serving cached flags until something else restarts the client. Unrecoverable 4xx (e.g. 401/403) and 401 shutDown() behavior are unchanged.

Unit tests cover retryable classification for 400/408/429 and a 429-then-SSE reconnect scenario.

Reviewed by Cursor Bugbot for commit 00a33ae. Bugbot is set up for automated code reviews on this repo. Configure here.

The legacy streaming data source treated every 4xx response as
non-retriable, so a rate-limit (429) on the stream connection
permanently stopped the stream until an app lifecycle event rebuilt
the data source. Classify stream HTTP errors with
LDUtil.isHttpErrorRecoverable, matching the FDv2 synchronizers and
other SDKs: 400, 408, and 429 now fall through to the EventSource
retry path. 401 handling is unchanged.
@kinyoklion
kinyoklion marked this pull request as ready for review July 30, 2026 22:02
@kinyoklion
kinyoklion requested a review from a team as a code owner July 30, 2026 22:02
@tanderson-ld
tanderson-ld self-requested a review July 31, 2026 14:14
@kinyoklion
kinyoklion merged commit 2ad5e6f into main Jul 31, 2026
6 checks passed
@kinyoklion
kinyoklion deleted the rlamb/sdk-2824/fdv1-stream-429-recoverable branch July 31, 2026 17:15
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.

3 participants