growthbook / growthbook/growthbook-sdk-java
Multi-user mode GrowthBookClient never enables SSE reconnection (retryOnFailure hardwired to false)
- Dominant language
- Java
- Stars
- 17
- Forks
- 18
- Avg merge
- 16m
- Merged PRs (30d)
- 1
Description
### Summary
The SSE reconnection support added for #52 (`GBFeaturesRepository.initialize(Boolean retryOnFailure)`) is never reachable from the multi-user mode `GrowthBookClient`: it always calls the no-arg `repository.initialize()`, which delegates to `initialize(false)`, and `Options` exposes no way to opt in. As a result, any app using `GrowthBookClient` with `FeatureRefreshStrategy.SERVER_SENT_EVENTS` permanently stops receiving feature updates after the first abnormal SSE stream failure — silently.
### Affected versions
Verified against the published sources of **0.10.6** and **0.10.10** (latest release), and against current `main`.
### Details (0.10.6 / 0.10.10)
The chain:
1. `GrowthBookClient.initialize()` → `repository.initialize()` (no-arg)
2. `GBFeaturesRepository.initialize()` → `initialize(false)` — `retryOnFailure` hardwired to `false`
3. In the SSE event source listener created by `createEventSourceListenerAndStartListening(retryOnFailure)`:
```java
@Override
public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable Response response) {
super.onFailure(eventSource, t, response);
if (retryOnFailure) {
createEventSourceListenerAndStartListening(true);
...
}
}
```
With `retryOnFailure == false`, an abnormal stream failure (connection reset, proxy/pod restart, mid-stream `IOException`) does **nothing**:
- no reconnect attempt;
- no polling fallback — `schedulePolling()` early-returns for `SERVER_SENT_EVENTS`;
- no observability — this path never invokes `onRefreshFailed(...)`, so registered `FeatureRefreshCallback.onError(...)` callbacks never fire.
The client then serves the last in-memory features forever (with `isCacheDisabled(true)` there is not even a cache to refresh from), and the application has no signal that anything is wrong.
Note that a *graceful* server close does reconnect on these versions (`onClosed` → `handler.onClose` → unconditional `createEventSourceListenerAndStartListening`), which makes the failure mode intermittent and easy to miss in testing: clean proxy restarts recover, abrupt failures don't.
### Still present on `main` (after #212)
#212 nicely reworked the retry machinery (`scheduleSseReconnect` with exponential backoff via `FeatureFetchRetryPolicy`, `Options.retryPolicy`), but the gate is still there and multi-user mode still never passes `true`:
```java
private synchronized void scheduleSseReconnect(Boolean retryOnFailure) {
if (!Boolean.TRUE.equals(retryOnFailure)
|| this.shuttingDown.get()
|| !this.sseReconnectScheduled.compareAndSet(false, true)) {
return;
}
...
}
```
`GrowthBookClient.initializeFeaturesRepository(...)` still calls the no-arg `repositorySnapshot.initialize()`, so `Options.retryPolicy` is configurable but the SSE reconnect it parameterizes is unreachable from multi-user mode.
There is also a behavioral tightening on `main`: the graceful-close path now routes through `scheduleSseReconnect(retryOnFailure)` as well, so once this ships, multi-user mode + SSE will no longer reconnect even on a graceful close (which 0.10.x did unconditionally). That widens this issue rather than fixing it.
### Reproduction sketch
1. Build a `GrowthBookClient` (multi-user mode) with `refreshStrategy = SERVER_SENT_EVENTS` pointing at a GrowthBook Proxy; call `initialize()`.
2. Kill the proxy abruptly (or drop the TCP connection), then bring it back.
3. Change a feature flag.
4. The client never reconnects, `FeatureRefreshCallback` stays silent, and evaluations keep returning the pre-failure values indefinitely.
### Suggested fix
Either of:
- Have `GrowthBookClient` call `repository.initialize(true)` — with #212's bounded backoff this seems like a safe default for a long-lived server-side client; or
- Expose the flag on `Options` (e.g. `sseReconnectOnFailure`) and thread it through to `initialize(...)`.
Happy to send a PR for either direction if you have a preference.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at GrowthBookClient.initializeFeaturesRepository and trace its call to GBFeaturesRepository.initialize(), then read the retryOnFailure gate in createEventSourceListenerAndStartListening and scheduleSseReconnect. Done means multi-user GrowthBookClient instances using SERVER_SENT_EVENTS reconnect after abnormal failures, honor the configured retry policy, and preserve the intended refresh or error behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 66/100