Commit dd7b0cd
authored
feat: server SDK RETRY-spec conformance in FDv1 streaming and polling (#200)
## Summary
Implements RETRY-spec conformance in the Java server SDK's FDv1
streaming and polling data sources
([SDK-2789](https://launchdarkly.atlassian.net/browse/SDK-2789)). This
PR is scoped to the server SDK only; the classifier helpers it consumes
ship separately in #204.
**Behavioral change.** HTTP responses that today cause an FDv1 data
source to permanently stop (notably 401 / 403 / other 4xx) and TLS /
certificate validation failures are no longer terminal. Streaming enters
an extended-regime backoff (5 min initial → 1 hr max, doubling); polling
continues at its configured cadence but engages the extended-regime wait
after an UNEXPECTED failure. Either regime returns to normal after 60 s
of continuous healthy operation (streaming) or two consecutive
successful polls (polling).
**Scope.** FDv1 streaming and polling under `lib/sdk/server/`. FDv2,
event delivery, and other network callers are unchanged.
## What changed
- **`PollingStrategy`** — New state-machine encapsulation with
`onFailure(class)` / `onSuccess()` / `nextWait()`. State: `n` (formula
input), `initialDelay`, `maxDelay`, `priorPollWasSuccessful`. Wait floor
is `max(pollInterval, T − J)`; two consecutive successes reset from
extended → normal.
- **`PollingProcessor`** — Rewired to a self-driven loop that consults
`strategy.nextWait()` between attempts. The `State.OFF` permanent-stop
path is removed; the state stays `INITIALIZING` or `INTERRUPTED` with a
`lastError`.
- **`StreamProcessor`** — Consumes okhttp-eventsource's new
multi-strategy retry API from
[launchdarkly/okhttp-eventsource#110](launchdarkly/okhttp-eventsource#110).
On `UNEXPECTED` classification it calls `activateRetryDelayStrategy` on
the underlying `EventSource` to switch into the extended-regime
`RetryDelayStrategy`. The library's built-in healthy-op reset returns
the SDK to normal-regime timing after 60 s of continuous connectivity.
- **`DataSourceStatusProvider` docs** — `State.INITIALIZING`,
`State.OFF`, `State.INTERRUPTED`, and `getStateSince` OFF-case Javadocs
updated to reflect the new semantics (no HTTP-error → OFF transition).
Aligned with the Go server SDK's parallel doc adjustments.
- **`LDClient` constructor Javadoc** — Wording tightened so a "wrong SDK
key" scenario is described as ongoing retry in the background, not as an
"unsuccessful initialization" that reads as terminal.
- **Contract-test service** — Declares
`retry-conformance-fdv1-streaming` and `retry-conformance-fdv1-polling`
capabilities.
The classifier this SDK depends on — `FailureClass` and
`HttpErrors.classify*` helpers — ships in #204.
## Testing
- **Unit tests** — Full suite green. New coverage: `PollingStrategyTest`
(strategy state machine), plus extended-regime timing-observation tests
in `StreamProcessorTest`. Existing 401 / 403 tests were rewritten to
assert extended-regime retry rather than permanent stop.
- **Contract tests via
[sdk-test-harness#404](launchdarkly/sdk-test-harness#404
— All 7 RETRY-conformance test cases pass end-to-end at production
timing (5-minute extended-initial delay). Total wall clock ~12 min via
parallel shards.
## Test plan for reviewers
- [x] Verify `PollingStrategy` transition semantics: normal → extended
fires exactly once per `UNEXPECTED` failure; two consecutive successful
polls fully reset (`n = 0`, delay bounds back to normal, `inExtended`
cleared so a subsequent `UNEXPECTED` re-triggers the transition).
- [ ] Verify `StreamProcessor.handleError` ordering: classifier → regime
switch → `updateStatus(INTERRUPTED, …)`, unconditionally returning true
so the eventsource keeps retrying.
- [x] Review the `DataSourceStatusProvider` Javadoc changes for accuracy
vs. the current state machine.
- [x] Code comments deliberately describe *current behavior* only — no
spec section refs, no historical framing ("previously", "no longer"), no
cross-SDK references. Confirm you'd expect a reader to find that
acceptable.
## Dependencies (why CI is red)
Two unreleased upstream artifacts:
- **`launchdarkly-java-sdk-internal ≥ 1.11.0`** — Must be released
before this PR can build against Maven Central. Ships via
#204 + its release-please chore.
- **`okhttp-eventsource ≥ 5.0.0`** — Must be released before this PR can
build against Maven Central. Ships via
[launchdarkly/okhttp-eventsource#110](launchdarkly/okhttp-eventsource#110).
Once both are released, bump their versions in
`lib/sdk/server/build.gradle` and CI will go green. Locally, the branch
builds against `mavenLocal()` snapshots of both.
[SDK-2789]:
https://launchdarkly.atlassian.net/browse/SDK-2789?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Overview**
> **FDv1 data sources no longer treat HTTP auth and other “unexpected”
failures as terminal.** Streaming and polling keep retrying in the
background with classified backoff instead of moving to `State.OFF`
(e.g. 401/403).
>
> **Polling** switches from fixed-rate scheduling to a self-driven loop
backed by new **`PollingStrategy`**: normal cadence at `pollInterval`,
extended exponential backoff (default 5 min → 1 hr cap, jitter) after
`FailureClass.UNEXPECTED`, reset after two consecutive successes.
>
> **Streaming** adopts **okhttp-eventsource 5.x** multi-regime
`RetryDelayStrategy`: normal reconnect caps at 30s; on unexpected
failures the SDK activates an extended strategy (5 min → 1 hr) and
relies on a 60s healthy-connection threshold to return to normal timing.
>
> **Public docs and tests** align with the new model:
`DataSourceStatusProvider` Javadoc and `LDClient` init wording no longer
describe HTTP errors as permanent shutdown; contract-test service
advertises retry-conformance capabilities; unit/e2e tests assert
continued retry and extended-regime behavior. Dependencies bump
**`launchdarkly-java-sdk-internal`** and **`okhttp-eventsource`** for
`HttpErrors` classification and strategy APIs.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
50513bc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
BEGIN_COMMIT_OVERRIDE
feat: conform FDv1 streaming and polling data sources to the RETRY
specification
HTTP responses that previously caused a data source to permanently stop
(notably 401, 403, and other 4xx), and TLS certificate validation
failures no longer terminate it. Streaming enters an extended backoff
regime starting at 5 minutes and doubling to a 1 hour ceiling; polling
continues at its configured interval but engages the extended regime
after an unexpected failure. Streaming returns to normal backoff after
60 seconds of continuous healthy operation; polling returns to its
normal cadence after two consecutive successful polls.
Two consequences are visible to applications.
DataSourceStatusProvider.State.OFF is now reached only by explicit
shutdown, not by an HTTP error, so applications monitoring for OFF to
detect an invalid SDK key will no longer see it. And because an invalid
SDK key no longer short circuits initialization, the LDClient
constructor waits out the full startWait timeout rather than returning
as soon as the 401 arrives.
END_COMMIT_OVERRIDE1 parent 717908b commit dd7b0cd
13 files changed
Lines changed: 922 additions & 170 deletions
File tree
- lib/sdk/server
- contract-tests/service/src/main/java/sdktest
- src
- main/java/com/launchdarkly/sdk/server
- interfaces
- test/java/com/launchdarkly/sdk/server
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
71 | 71 | | |
72 | 72 | | |
73 | 73 | | |
74 | | - | |
| 74 | + | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
Lines changed: 3 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
48 | 50 | | |
49 | 51 | | |
50 | 52 | | |
| |||
Lines changed: 4 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
149 | 152 | | |
150 | 153 | | |
151 | 154 | | |
| |||
196 | 199 | | |
197 | 200 | | |
198 | 201 | | |
| 202 | + | |
199 | 203 | | |
200 | 204 | | |
201 | 205 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
161 | | - | |
| 161 | + | |
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| |||
Lines changed: 62 additions & 35 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
| |||
21 | 23 | | |
22 | 24 | | |
23 | 25 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | 26 | | |
28 | 27 | | |
29 | 28 | | |
| 29 | + | |
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
36 | 42 | | |
37 | | - | |
38 | 43 | | |
39 | 44 | | |
40 | 45 | | |
41 | 46 | | |
42 | 47 | | |
43 | 48 | | |
44 | 49 | | |
| 50 | + | |
45 | 51 | | |
46 | 52 | | |
47 | 53 | | |
48 | 54 | | |
49 | 55 | | |
50 | 56 | | |
| 57 | + | |
51 | 58 | | |
52 | 59 | | |
53 | 60 | | |
| |||
59 | 66 | | |
60 | 67 | | |
61 | 68 | | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
69 | 74 | | |
70 | 75 | | |
71 | 76 | | |
72 | 77 | | |
73 | 78 | | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
74 | 83 | | |
75 | 84 | | |
76 | 85 | | |
77 | 86 | | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | 87 | | |
82 | | - | |
83 | | - | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
84 | 92 | | |
85 | 93 | | |
86 | | - | |
87 | 94 | | |
88 | 95 | | |
89 | | - | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
90 | 113 | | |
91 | 114 | | |
92 | 115 | | |
| |||
96 | 119 | | |
97 | 120 | | |
98 | 121 | | |
99 | | - | |
| 122 | + | |
100 | 123 | | |
101 | 124 | | |
102 | | - | |
| 125 | + | |
103 | 126 | | |
104 | 127 | | |
105 | 128 | | |
106 | 129 | | |
107 | 130 | | |
108 | 131 | | |
| 132 | + | |
109 | 133 | | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
121 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
122 | 139 | | |
123 | 140 | | |
124 | | - | |
125 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
126 | 147 | | |
127 | 148 | | |
128 | | - | |
| 149 | + | |
| 150 | + | |
129 | 151 | | |
130 | 152 | | |
131 | 153 | | |
132 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
133 | 160 | | |
134 | 161 | | |
135 | 162 | | |
Lines changed: 145 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
0 commit comments