Skip to content

fix: Reconnect the stream after non-HTTP read errors - #128

Open
kallevmercury wants to merge 6 commits into
launchdarkly:mainfrom
kallevmercury:kallev/stream-reconnect-non-http-errors
Open

kallevmercury wants to merge 6 commits into
launchdarkly:mainfrom
kallevmercury:kallev/stream-reconnect-non-http-errors

Conversation

@kallevmercury

@kallevmercury kallevmercury commented Nov 10, 2026 •

Copy link
Copy Markdown

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

None.

Describe the solution you've provided

Apps on the default streaming data source used to stop getting flag updates until restart after any of these stream read errors:

  • a connection reset
  • a TLS error
  • a multi-byte UTF-8 character split across two reads

tryHTTP only catches HttpException, so these errors ended the streaming thread with no log or reconnect, and an uninitialized app never initialized. The fix has two parts:

  • The decoder holds a partial UTF-8 character until the next read completes it (streamDecodeUtf8).
  • The SDK logs other read errors and retries them with the existing backoff. close still stops the stream.

The retry delay also used to overflow Int after 45 failed attempts at the default 1 s delay, so retries ran with no wait. Computing the delay in Integer keeps it at the 30 s maximum.

Describe alternatives you've considered

Lenient UTF-8 decoding would avoid the error but replace the split character with U+FFFD, corrupting the flag. The handling covers only the body read, so an invalid SDK key still stops the stream instead of retrying forever.

Additional context

The tests in test/Spec/Streaming.hs run the real streaming thread against scripted reads from a fake connection. Each test lands one commit before its fix: the reset and UTF-8 tests fail on commit 1, the backoff test on commit 3.

I have also replicated this condition on the other LD SDKs, and they log an error and attempt to reconnect:

  • Python: [WARNING] ldclient.util: Error on stream connection: ("Connection broken: ConnectionResetError(54, ...)") - will retry in 0.1s.
  • Node: Received I/O error (aborted) for streaming request - will retry, then Will retry stream connection in 63 milliseconds.

The socket reset and split UTF-8 tests fail on this commit. The streaming
thread exits after one connection, without retrying or logging, so the
SDK stops receiving flag updates.
A socket reset, TLS error, or a UTF-8 character split across two reads
threw a non-HttpException from the body read, which ended the streaming
thread with no log and no reconnect. The body read now decodes UTF-8
incrementally, and other read errors are logged and rethrown as an
HttpException so the existing backoff and reconnect apply. Async
exceptions still pass through so close stops the thread.
backoffCeiling is the pure part of calculateDelay, with no behavior
change. The new test fails on this commit: at attempt 46 the Int
product overflows to a negative ceiling, so retries run with no delay.
… delay

After 45 failed attempts at the default 1 s initial delay, the backoff
product overflowed Int and the SDK retried with no delay. Capping the
exponent at 15 still reaches the 30 s maximum for any initial delay of
1 ms or more.
Copilot AI balanced review requested due to automatic review settings November 10, 2026 20:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The retry backoff multiplication can overflow Int on 32-bit runtimes before the cap is applied, producing negative delays and failing the new regression test.

1 open issue
What changed in this PR

This PR improves streaming data source recovery so interrupted connections do not permanently stop flag updates.

Changes:

  • Retries non-HTTP stream read errors while preserving asynchronous cancellation.
  • Decodes UTF-8 incrementally to handle characters split across reads.
  • Caps the backoff exponent and adds regression tests for reconnection, decoding, shutdown, and retry delays.
File Description
test/​Spec/​Streaming.hs Adds scripted stream tests and a backoff regression test.
src/​LaunchDarkly/​Server/​Network/​Streaming.hs Adds incremental decoding, read-error recovery, and capped backoff calculation.

Review effort: Balanced

@kallevmercury
kallevmercury marked this pull request as ready for review November 10, 2026 23:39
@kallevmercury
kallevmercury requested a review from a team as a code owner November 10, 2026 23:39
The new assertion fails on this commit: backoffCeiling maxBound 1 wraps
to -1000. The same Int overflow hits the default 1 s delay on 32-bit runtimes.
…erflow

The capped exponent still let the Int product wrap on 32-bit runtimes at the
default 1 s delay, and on any runtime for a very large initial delay.
Copilot AI balanced review requested due to automatic review settings November 10, 2026 23:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues block approval of the streaming recovery and UTF-8 decoding changes.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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

None yet

Development

Successfully merging this pull request may close these issues.

2 participants