Skip to content

fix: Keep retrying the streaming connection after an unexpected HTTP error response - #352

Draft
jsonbailey wants to merge 1 commit into
jb/sdk-2804/identify-retry-docsfrom
jb/sdk-3324/streaming-retry
Draft

jsonbailey wants to merge 1 commit into
jb/sdk-2804/identify-retry-docsfrom
jb/sdk-3324/streaming-retry

Conversation

@jsonbailey

Copy link
Copy Markdown

Summary

Stacked on the identify docs PR. The FDv1 streaming data source no longer stops for good after an HTTP error such as a 401.

  • The SDK owns the reconnect delay. It makes the SSE client with autoReconnect: false, closes it on any failure, and reconnects after the RetryState delay: 1 second up to 30 seconds normally, and 5 minutes up to 1 hour after an unexpected failure. The backoff resets after 60 seconds of healthy streaming.
  • Logs use warn for normal failures and error for unexpected ones, state the real delay, and add a credential hint only for 401 and 403.
  • A restart for a connection that has already been replaced is ignored, so a late invalid message cannot close a newer, healthy connection.
  • FDv2 streaming maps the new SSE errors to interrupted, as Android FDv2 does, and logs the failure kind.
  • A cached identify no longer reports an unhandled error when the first stream attempt fails.
  • Contract test service: declares retry-conformance-fdv1-streaming.
  • common_client needs the released launchdarkly_event_source_client and launchdarkly_dart_common versions before this merges. The pin bumps follow those releases.


switch (event) {
case MessageEvent():
_retryState.recordSuccess();

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Any message, including ping, starts the 60 s healthy measurement. iOS (#533) and Android (#406) do the same. The spec clarification is sdk-specs #276.

if (_stopped) {
return;
}
_generation++;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

iOS and Android handle stream messages synchronously on the stream's own thread, so they cannot hit this. Here, DataSourceManager handles events asynchronously, so a late invalidMessage could restart a newer connection. The generation token ignores a restart for a connection that has been replaced.

.asyncMap((event) async => (event, await handleEvent(event)))
.listen((handled) {
final (event, status) = handled;
if (status == MessageStatus.invalidMessage) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Known follow-up: messages already queued from a restarted connection are still applied until the next full put. iOS does the same. Android drops them, because it restarts after the current message. Tracked separately.

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.

1 participant