Skip to content

feat: Apply RETRY to FDv2 synchronizer orchestration - #252

Merged
beekld merged 21 commits into
mainfrom
bklimt/SDK-3402/retry-parking
Nov 12, 2026
Merged

beekld merged 21 commits into
mainfrom
bklimt/SDK-3402/retry-parking

Conversation

@beekld

@beekld beekld commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Applies the RETRY behavior to FDv2 synchronizer orchestration in the server SDK. A synchronizer that fails is parked and retried later, instead of being blocked for good.

Parking

  • A synchronizer that fails unexpectedly is parked: a terminal error, an exception from next(), a failure to build, or shutting down on its own. The park delay starts at 5 minutes, doubles with each failure up to 1 hour, and has jitter. After a park, the next synchronizer starts after a failover delay of 0.5-1 seconds.
  • A synchronizer's park count resets when it meets its reset threshold. For polling, that's two successful polls in a row. For streaming, it's staying connected for 60 seconds.
  • When every synchronizer is parked, the data system waits for the earliest unpark time. Synchronization stops only when the data source is closed.
  • When the fallback condition is met but no other synchronizer is available, the current synchronizer keeps running.

Directed fallback

  • The fallback to FDv1 now ends after a TTL, which defaults to 1 hour with jitter. The data system then restarts the FDv2 synchronizers.
  • A directive received during the fallback extends the TTL.
  • A directive with no FDv1 fallback synchronizer configured no longer halts the data system. It reports INTERRUPTED and restarts the FDv2 synchronizers when the TTL ends.

Conditions removed

  • The Conditions class and FDv2DataSourceConditions are removed. They weren't compatible with the new parking and FDv1 fallback timers, so the synchronizer loop is simplified to check all of its timers each time it wakes.

Not included

  • Honoring Retry-After.
  • Reading the TTL from the response. Every directive uses the 1-hour default.

Note

Overview
Replaces the old condition/timer model (FDv2DataSourceConditions, FDv2DataSource.Conditions) with a single synchronizer loop that wakes on results or deadlines (fallback, recovery, unpark, FDv1 TTL). Failed synchronizers are parked with exponential backoff (5 min → 1 h, jitter) instead of being blocked for the process lifetime; terminal errors and build failures park the source rather than permanently blocking it.

Adds AsyncLatch so close() and timed waits can race CompletableFuture.anyOf without leaking completion nodes on long-lived streams.

FDv1 fallback is time-bounded (default 1 h, jitter): SourceManager tracks end time, blocks FDv2 sources during fallback, and returns when TTL expires. Server directives extend TTL or switch to FDv1 without halting the whole data system when no FDv1 synchronizer exists (reports INTERRUPTED and waits for TTL).

Reset threshold is propagated via FDv2SourceResult.resetThresholdMet — polling (2 successes), streaming (60 s healthy), FDv1 adapter (2 VALID) — and resets park counts. HTTP/transport classification (FailureClass) turns unexpected errors into terminal results in polling, streaming, and the FDv1 synchronizer adapter.

close() releases the latch, shuts down sources, and joins the run thread (5 s cap) before reporting OFF.

Reviewed by Cursor Bugbot for commit 94f7478. Bugbot is set up for automated code reviews on this repo. Configure here.

@beekld
beekld force-pushed the bklimt/SDK-3402/retry-parking branch from 0cf207f to 8aa8c77 Compare November 6, 2026 00:49
@beekld
beekld marked this pull request as ready for review November 6, 2026 00:54
@beekld
beekld requested a review from a team as a code owner November 6, 2026 00:55
@beekld
beekld added this pull request to stack #254 November 6, 2026 03:27
@beekld
beekld force-pushed the bklimt/SDK-3402/retry-parking branch from f78d65f to 0dafa42 Compare November 6, 2026 17:37

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0dafa42. Configure here.

@beekld
beekld force-pushed the bklimt/SDK-3402/retry-parking branch from 0dafa42 to a700bff Compare November 10, 2026 21:42
@beekld
beekld requested a review from tanderson-ld November 11, 2026 01:53
@beekld
beekld merged commit 6a4aac5 into main Nov 12, 2026
30 checks passed
@beekld
beekld deleted the bklimt/SDK-3402/retry-parking branch November 12, 2026 18:42
beekld added a commit that referenced this pull request Nov 12, 2026
## Summary

The FDv2 synchronizer loop measured its deadlines against the wall
clock. When the system clock stepped backwards, every pending deadline
was delayed by the size of the step. If every synchronizer was parked
for 5 minutes and the clock stepped back an hour, no synchronizer ran
for 65 minutes.

These deadlines now use `MonotonicTime`, a small value class over
`System.nanoTime()`, so changes to the system clock don't move them.

Stacked on #252.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> FDv2 synchronizer orchestration previously scheduled **recovery**,
**fallback**, **park/unpark**, and **FDv1 fallback TTL** deadlines using
wall-clock `Instant`. A backward system clock step could postpone those
deadlines and keep sources parked or on FDv1 longer than intended.
> 
> This PR introduces **`MonotonicTime`** (backed by `System.nanoTime()`)
and wires it through `FDv2DataSource`, `SourceManager`, and
`SynchronizerFactoryWithState` for all internal wait/deadline logic
(`awaitUntil`, `earliest`, parking, fallback end times).
**`MonotonicTimeTest`** covers comparison and duration math across
nanosecond counter overflow.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
d8cd1e1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
beekld pushed a commit that referenced this pull request Nov 12, 2026
🤖 I have created a release *beep* *boop*
---


##
[7.18.0](launchdarkly-java-server-sdk-7.17.5...launchdarkly-java-server-sdk-7.18.0)
(2026-11-12)


### Features

* Apply RETRY to FDv2 synchronizer orchestration
([#252](#252))
([6a4aac5](6a4aac5))


### Bug Fixes

* Include the big segments status on evaluation reasons when a flag
fails to evaluate
([#258](#258))
([ce08ae8](ce08ae8))
* Make TestData valueForAll return its value when targeting is off
([#251](#251))
([db17ab9](db17ab9))
* Measure synchronizer deadlines with a monotonic clock
([#253](#253))
([3c7da2c](3c7da2c))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> This **Release Please** PR cuts **launchdarkly-java-server-sdk
7.18.0** by bumping version metadata only:
`.release-please-manifest.json`, `lib/sdk/server/gradle.properties`, and
`Version.SDK_VERSION` move from **7.17.5** to **7.18.0**.
> 
> `CHANGELOG.md` gains a **7.18.0** section that records what ships in
this artifact (already merged elsewhere): **RETRY** on FDv2 synchronizer
orchestration; big-segments status on evaluation reasons when evaluation
fails; `TestData` `valueForAll` honoring its value when targeting is
off; synchronizer deadlines measured with a monotonic clock. This diff
does not contain those implementation changes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
aa18674. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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