fix: Stop the FDv1 stream on the stream thread after an unrecoverable HTTP error - #402
Merged
kinyoklion merged 1 commit intoSep 25, 2026
Merged
Conversation
… HTTP error The decision to stop after a 401, 403, or other unrecoverable status was made in the onError callback, which runs on a different thread from the stream reader. BackgroundEventSource reconnected after the retry delay before that stop() took effect, so the SDK opened a second connection it should not have made. A ConnectionErrorHandler now returns SHUTDOWN for unrecoverable statuses; BackgroundEventSource checks it on the stream thread before any reconnect.
tanderson-ld
approved these changes
Sep 23, 2026
kinyoklion
marked this pull request as ready for review
September 25, 2026 16:51
kinyoklion
deleted the
rlamb/sdk-3180/fdv1-stream-stop-on-unrecoverable
branch
September 25, 2026 17:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The FDv1
StreamingDataSourcesometimes opens a second stream connection after an unrecoverable HTTP status (401, 403, 405, ...), even though it has decided to stop. The v2 contract testsstreaming/retry behavior/do not retry after unrecoverable HTTP error on initial connectand.../on reconnectfail intermittently because of it, both in CI and locally against unmodifiedmain(3 of 4 runs of that test group).Cause. The decision to stop lives in the
BackgroundEventHandler.onErrorcallback.BackgroundEventSourcedispatches that callback to its event executor and then, with noConnectionErrorHandlerconfigured, goes straight back to reading, which reconnects after the retry delay. The callback'sstop()spawns yet another thread to close the source. Two thread hops versus a reconnect delay of 1 ms (the harness setting) is a race the reconnect usually wins; the default 1 s delay makes it rare in production but does not remove it.Fix. Give the
BackgroundEventSourceaConnectionErrorHandlerthat returnsSHUTDOWNfor aStreamHttpErrorExceptionwhose status is not recoverable perLDUtil.isHttpErrorRecoverable, andPROCEEDotherwise.BackgroundEventSourceconsults it on the stream thread before the next reconnect attempt, so no second connection can be made. TheonErrorbookkeeping (error callback, 401 guard, sink shutdown,stop()) is unchanged; recoverable statuses still retry with backoff.Tests. Two new
StreamingDataSourceTestcases use a 1 ms reconnect delay and a sequential server: an unrecoverable status on the initial connect, and one on the reconnect after a dropped stream. Each asserts that no further request arrives. Both fail on every run without the fix (3 of 3) and pass on every run with it (5 of 5).Verified on an emulator with the v2.41.0 harness: the
streaming/retry behaviorgroup passed 6 of 6 runs against this branch (unmodifiedmainfailed 3 of 4), and the full v2 suite passes (848 total, 832 ran, exit 0). SDK unit tests: 732 pass.Note
Overview
Fixes a race where FDv1 streaming could open another connection after an unrecoverable HTTP status (401, 403, etc.) even though
onErrorhad already decided to stop. Stop logic ran on the event executor whileBackgroundEventSourcecould reconnect on the stream thread beforestop()took effect—especially visible with a 1 ms retry delay in contract tests.StreamingDataSourcenow configures aConnectionErrorHandleronBackgroundEventSourcethat returnsSHUTDOWNwhen the error is aStreamHttpErrorExceptionwith a statusLDUtil.isHttpErrorRecoverablerejects, andPROCEEDotherwise. That decision runs on the stream thread before the next reconnect, so a second request cannot win the race. ExistingonErrorbehavior (callbacks, 401 guard, sink shutdown, recoverable retries) is unchanged.Tests add configurable
initialReconnectDelayMillisin the test helper and two HTTP-server cases: unrecoverable status on initial connect and on reconnect after a successful stream—each asserts no further requests after the failure.Reviewed by Cursor Bugbot for commit cfa8126. Bugbot is set up for automated code reviews on this repo. Configure here.