Skip to content

fix: make FDv1 fallback TTL-based and recover to FDv2 after the TTL - #413

Draft
beekld wants to merge 2 commits into
mainfrom
devin/1792430263-fdv1-fallback-ttl
Draft

beekld wants to merge 2 commits into
mainfrom
devin/1792430263-fdv1-fallback-ttl

Conversation

@beekld

@beekld beekld commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

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 (unit tests only; not run on device/emulator)

Related issues

SDK-2991 (part of SDK-2935); spec change sdk-specs#251 (DATASYSTEM v2 1.6 / CSFDV2 8.3). Reference: cpp-sdks#631.

Describe the solution you've provided

FDv1 fallback is now TTL-based and never terminal.

  • TTL is read from the X-LD-FD-Fallback-TTL header (polling and streaming) and from goodbye.protocolFallbackTTL; a goodbye carrying that field is itself a fallback directive. A fallback directive is now carried as a @Nullable FDv1FallbackTTL on FDv2SourceResult (getFdv1Fallback(); null means no directive). The existing boolean factories are kept and map true to the default TTL.
  • A TTL in (0, 3600]s is used as-is; anything else (missing, malformed, 0, negative, > 3600) uses the 1 h default minus uniform [0, 1800]s jitter. This is resolved once, when FDv1FallbackTTL is parsed from the header or goodbye.
  • On fallback, FDv2DataSource stops the FDv2 synchronizers (retained in SourceManager), runs the FDv1 synchronizer if configured, and schedules recovery after the TTL. Recovery blocks FDv1 again and rebuilds a fresh FDv2 synchronizer. A repeated directive while on FDv1 only reschedules the TTL.
  • Fallback now happens even with no FDv1 synchronizer configured: no synchronizer runs (status INTERRUPTED) until the TTL elapses, then FDv2 restarts. Previously the directive was ignored in that case.
  • Single writer: only one synchronizer is active at a time; the outgoing one is closed before the next is built, and SourceManager now closes the active source when nothing can replace it.
Implementation details
  • SourceManager tracks which synchronizers fdv1Fallback() blocked, so recoverFromFdv1Fallback() unblocks only those (FDv2 sources that failed terminally stay blocked).
  • A streaming goodbye that carries a fallback directive does not reconnect the stream, since the data source closes that synchronizer anyway; a plain goodbye still reconnects as before.
  • The info log on fallback changed to include the TTL ("Server directed fallback to FDv1; FDv2 will be retried in N seconds.").
  • Tests: FDv1FallbackTTLTest (valid TTLs unchanged, invalid TTLs land in [1800, 3600], header/goodbye parsing), header and goodbye propagation in DefaultFDv2RequestorTest, FDv2PollingBaseTest, FDv2StreamingSynchronizerTest, and FDv2DataSourceTest recovery with and without an FDv1 synchronizer (using a 1 s server TTL). ./gradlew :launchdarkly-android-client-sdk:testDebugUnitTest javadoc passes locally.

Describe alternatives you've considered

Treating "no FDv1 synchronizer" as "ignore the directive" (old behavior); rejected because the spec requires stopping FDv2 and recovering after the TTL regardless.

Additional context

Deliberately unchanged: the ordinary fallback/recovery condition timers (2 min / 5 min) between FDv2 synchronizers, and the contract tests (the harness has no coverage of fallback TTL).

Link to Devin session: https://app.devin.ai/sessions/b4e7a7ad5f06499695e4ce57a2ca6cc2
Open in Devin Desktop: https://app.devin.ai/desktop/session/b4e7a7ad5f06499695e4ce57a2ca6cc2?variant=devin
Requested by: @beekld

Co-Authored-By: Bee Klimt <bklimt@launchdarkly.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

Co-Authored-By: Bee Klimt <bklimt@launchdarkly.com>

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