Skip to content

feat(server): RETRY-spec conformance in FDv1 streaming and polling (SDK-2789) - #200

Draft
tanderson-ld wants to merge 1 commit into
mainfrom
ta/SDK-2789/retry-conformance-work
Draft

feat(server): RETRY-spec conformance in FDv1 streaming and polling (SDK-2789)#200
tanderson-ld wants to merge 1 commit into
mainfrom
ta/SDK-2789/retry-conformance-work

Conversation

@tanderson-ld

@tanderson-ld tanderson-ld commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements RETRY-spec conformance in the Java server SDK's FDv1 streaming and polling data sources. Per epic SDK-2775 and analogous to Go's reference implementation SDK-2788.

Behavioral change: HTTP responses that today cause a data source to permanently stop (notably 401, 403, other 4xx) and TLS/certificate validation failures are no longer terminal. Streaming enters an extended backoff regime (5 min → 1 hr, doubling); polling continues at its configured cadence with extended-regime waits between failing polls. Recovery from either regime uses RETRY §1.8's healthy-operation reset (60 s of continuous connectivity).

Scope: FDv1 streaming and polling data sources under `lib/sdk/server/`. FDv2 is deliberately out of scope for this epic (SDK-2775 defers FDv2 to a future epic); nothing in `datasourcev2/` or the DataSystem-related code paths is touched.

Highlights

  • `FailureClass` enum + local classifier. HTTP status per §1.6, transport exception per §1.7 (TLS/cert → UNEXPECTED). Classifier stays local to java-server-sdk; no changes to `launchdarkly-java-sdk-internal`.
  • `PollingStrategy` state-machine encapsulation. State: `n`, `initialDelay`, `maxDelay`, `priorPollWasSuccessful`. Methods: `onFailure(class)`, `onSuccess()`, `nextWait()`. Wait floor `max(pollInterval, T − J)` per RETRY §1.4.4 override. Two-consecutive-successes reset per §1.8.1.
  • `PollingProcessor` rewire. From `scheduleAtFixedRate` to a self-driven loop using `strategy.nextWait()`. Removed the `State.OFF` permanent-stop path.
  • `StreamProcessor` regime switching. Consumes okhttp-eventsource's new `setInitialRetryDelayMillis` / `setMaxRetryDelayMillis` (see launchdarkly/okhttp-eventsource#109). On UNEXPECTED classification, transitions retry timing into the extended regime; on `activeSince` elapsed ≥ `retryResetInterval`, transitions back to normal. Library's built-in healthy-op reset is disabled so the SDK owns regime state.
  • Test-only builder knobs. Package-private setters on the concrete Impl classes; extends the existing `pollIntervalWithNoMinimum` precedent.
  • Cross-module bridge. New `com.launchdarkly.sdk.server.internal.DataSourceInternalHelpers` (delegating through package-private `ComponentsInternalBridge`) gives cross-module callers access to the test-only knobs. Package name signals internal-only surface.
  • Contract test service. Declares `retry-conformance-fdv1-streaming` and `retry-conformance-fdv1-polling` capabilities. New RETRY-related fields on `SdkConfigStreamParams` and `SdkConfigPollingParams`. `SdkClientEntity` wires them via the internal bridge.

Testing

  • Unit tests: full suite BUILD SUCCESSFUL. New tests: `FailureClassTest` (22 cases), `PollingStrategyTest` (9 cases). 401/403 tests renamed and rewritten to assert extended-regime retry instead of permanent stop.
  • Contract tests via `launchdarkly/sdk-test-harness#404` (v2 branch, RETRY-conformance tests): 4889 tests pass end-to-end, including the extended-regime paired-bound assertion.

Test plan for reviewers

  • Confirm the FailureClass mapping (§1.6 / §1.7) matches your expectations, particularly TLS/cert = UNEXPECTED.
  • Confirm the `.internal` package as the marker for cross-module test-only access is acceptable as a new convention.
  • Sanity-check `StreamProcessor.handleError` — classifier + regime-transition + healthy-op-reset ordering.
  • The library's built-in healthy-op reset is now disabled in `StreamProcessor`, since the SDK owns regime state. Confirm this is the right call.
  • Sanity-check the mixed-classification transition test in `PollingStrategyTest.mixedClassificationNormalThenUnexpectedStartsAtExtendedInitial`.

Dependency

CI will be red on this PR until launchdarkly/okhttp-eventsource#109 is released to Maven Central. The `setInitialRetryDelayMillis` / `setMaxRetryDelayMillis` methods this SDK relies on are only in that PR's branch. Once released, bump `okhttp-eventsource` version in `lib/sdk/server/build.gradle`.

…polling data sources (SDK-2789)

Per epic SDK-2775 and the server-sdk-guide.md at
individual/tanderson/tickets/SDK-2775/implementation/ in sdk-scratchpad.
Analogous to the Go reference implementation SDK-2788.

The behavioral change: HTTP responses that today cause a data source to
permanently stop (notably 401, 403, other 4xx) and TLS/certificate
validation failures are no longer terminal. Streaming enters an extended
backoff regime (5 min -> 1 hour, doubling); polling continues at its
configured cadence with extended-regime waits between failing polls.
Recovery from either regime uses RETRY spec §1.8's healthy-operation
reset (60 s of continuous connectivity).

Scope: FDv1 streaming and polling data sources under
`lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/`. FDv2 is out
of scope for this epic (SDK-2775 explicitly defers FDv2 to a future
epic); nothing in `datasourcev2/` or the DataSystem-related code paths
is touched.

Highlights:
- FailureClass enum + local classifier: HTTP status per §1.6, transport
  exception per §1.7 (TLS/cert => UNEXPECTED). Classifier stays local to
  java-server-sdk; no changes to launchdarkly-java-sdk-internal.
- PollingStrategy: new state machine encapsulation with onFailure(class)
  / onSuccess() / nextWait() methods, per server-sdk-guide §Polling.
  State: n (formula input), initialDelay, maxDelay, priorPollWasSuccessful.
  Wait floor: max(pollInterval, T - J) per §1.4.4 override.
  Two-consecutive-successes reset per §1.8.1.
- PollingProcessor: rewired from scheduleAtFixedRate to a self-driven
  loop using strategy.nextWait(). Removed the State.OFF permanent-stop
  path entirely; state stays INITIALIZING/INTERRUPTED with a lastError.
- StreamProcessor: consumes okhttp-eventsource's new
  setInitialRetryDelayMillis / setMaxRetryDelayMillis (see
  launchdarkly/okhttp-eventsource#109). On UNEXPECTED classification,
  transitions the retry timing into the extended regime; on activeSince
  elapsed >= retryResetInterval, transitions back to normal. Library's
  built-in healthy-op reset (retryDelayResetThresholdMillis) is disabled
  so the SDK owns regime state.
- Builder plumbing: new package-private setters on
  StreamingDataSourceBuilderImpl / PollingDataSourceBuilderImpl for
  extendedInitialReconnectDelay, retryResetInterval, and
  extendedInitialDelay -- follows the existing pollIntervalWithNoMinimum
  precedent.
- Internal bridge: com.launchdarkly.sdk.server.internal.DataSourceInternalHelpers
  gives cross-module callers (contract test service) access to the
  package-private test-only knobs. Public methods; package name signals
  internal-only surface.
- Contract test service: declares retry-conformance-fdv1-streaming and
  retry-conformance-fdv1-polling capabilities. New ExtendedInitialDelayMs
  and ResetThresholdMs fields on SdkConfigStreamParams; extendedInitialDelayMs
  on SdkConfigPollingParams. SdkClientEntity wires them via the internal
  bridge.

Tests:
- Unit tests: full test suite green (BUILD SUCCESSFUL in 2m 36s).
  New tests: FailureClassTest (22 cases), PollingStrategyTest (9 cases).
  Updated: 401/403 tests renamed and rewritten to assert extended-regime
  retry instead of permanent stop.
- Contract tests via sdk-test-harness#404 (v2 branch, RETRY-conformance
  tests): all 4889 tests pass end-to-end, including the specific
  extended-regime paired-bound assertions per server-sdk-guide
  §Testing considerations.

CI: intentionally red on this PR until launchdarkly/okhttp-eventsource#109
is released to Maven Central. The setInitialRetryDelayMillis /
setMaxRetryDelayMillis methods this SDK relies on are only in that PR's
branch. Once released, bump okhttp-eventsource version in
lib/sdk/server/build.gradle.
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