growthbook / growthbook/growthbook-sdk-java

Multi-user mode GrowthBookClient never enables SSE reconnection (retryOnFailure hardwired to false)

Open
#226 3 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.