Skip to content

fix: make FDv1 fallback TTL-based and never terminal - #469

Draft
beekld wants to merge 1 commit into
mainfrom
devin/1792430255-fdv1-fallback-ttl
Draft

beekld wants to merge 1 commit into
mainfrom
devin/1792430255-fdv1-fallback-ttl

Conversation

@beekld

@beekld beekld commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Makes the FDv2 data system's FDv1 fallback TTL-based, so the SDK always recovers to FDv2, per sdk-specs#251 (DATASYSTEM v2 1.6 / CSFDV2 8.3).

Related issues

SDK-2990 (part of SDK-2935). Reference: cpp-sdks#631.

Describe the solution you've provided

  • Reads the TTL from the X-LD-FD-Fallback-TTL header (streaming and polling, synchronizers and initializer) or from goodbye.protocolFallbackTTL. A streaming goodbye that carries protocolFallbackTTL now triggers fallback.
  • A TTL in (0, 3600] is used as-is, with no jitter. A missing, malformed, zero, negative, or > 3600 TTL uses the 1 hour default minus uniform random [0, 1800]s.
  • Fallback no longer replaces @synchronizer_builders. The FDv1 synchronizer runs until the TTL elapses, then it is stopped and FDv2 restarts from the primary synchronizer.
  • With no FDv1 synchronizer configured, the data system reports INTERRUPTED instead of halting with OFF, releases ready waiters, and retries FDv2 after the TTL.
  • Store writes still all happen on the single synchronizer thread. Each source is stopped (in consume_synchronizer_results's ensure) before the next one is built, so FDv1 and FDv2 never write concurrently.
Implementation details
  • LaunchDarkly::Impl::DataSystem.fdv1_fallback_ttl / .fdv1_fallback_ttl_from_headers (in polling.rb, next to fdv1_fallback_requested?) do the TTL normalization. Update and FetchResult gain an optional fdv1_fallback_ttl. FDv2 normalizes again, so a custom source that leaves it nil gets the jittered default.
  • FDv2#run_fdv1_fallback(ttl) runs the FDv1 synchronizer. consume_synchronizer_results takes a new recover_after: argument, which schedules a one-shot "recover" action that returns SyncResult::RECOVER. If FDv1 fails permanently, or no FDv1 synchronizer is configured, it waits on @stop_event for the rest of the TTL.
  • An initializer's fallback directive now starts the synchronizer phase in TTL-bound fallback.

Deliberately unchanged:

  • When no FDv2 synchronizers are configured (initializers only), an initializer fallback still switches to FDv1 for good (or OFF if none is configured), because there is no FDv2 synchronizer to recover to.
  • An in-band goodbye inside a polling response body is not parsed. The polling payload parser doesn't handle goodbye today. Polling fallback still comes from the response headers.
  • The existing "stays on FDv1 after fallback" test (default TTL >= 30 min) keeps its sleep 1.0 negative assertion. Only its name and comment were updated.
  • Contract tests: no changes. The fdv1-fallback capability is already advertised, and the scenarios live in the shared harness.

Testing: bundle exec rspec spec (1263 examples, 0 failures) and bundle exec rubocop (no offenses). New specs cover valid TTLs unchanged, invalid TTLs in [1800, 3600], jitter bounds, header and goodbye TTL propagation, recovery without an FDv1 synchronizer, and FDv1 being stopped before FDv2 restarts.

Describe alternatives you've considered

Clamping out-of-range TTLs to 3600 instead of defaulting them. Rejected because the spec says to use the jittered default.

Additional context

The requirements checklist above is left for the author to tick.

Link to Devin session: https://app.devin.ai/sessions/e2ff05b34a7b46deb8f5d82653515590
Open in Devin Desktop: https://app.devin.ai/desktop/session/e2ff05b34a7b46deb8f5d82653515590?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

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