Skip to content

Commit db19718

Browse files
committed
fix: Keep the override flags in the all flags state when the store fails
When the store cannot list its flags, the state without overrides is invalid and empty. Overrides only add to it, so the state now holds the override layer's flags and stays invalid, and the failure is logged as before. A client-side SDK bootstrapped from the state uses its flags whatever $valid says, so the overridden values still reach it.
1 parent aa73bd9 commit db19718

2 files changed

Lines changed: 38 additions & 10 deletions

File tree

‎lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/InputValidatingEvaluator.java‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public FeatureFlagsState allFlagsState(LDContext context, FlagsStateOption... op
208208

209209
boolean clientSideOnly = FlagsStateOption.hasOption(options, FlagsStateOption.CLIENT_SIDE_ONLY);
210210
DataStoreTypes.KeyedItems<DataStoreTypes.ItemDescriptor> flags;
211+
boolean valid = true;
211212
if (overrideFlags != null) {
212213
flags = overrideFlags;
213214
} else {
@@ -216,7 +217,17 @@ public FeatureFlagsState allFlagsState(LDContext context, FlagsStateOption... op
216217
} catch (Exception e) {
217218
logger.error("Exception from data store when evaluating all flags: {}", LogValues.exceptionSummary(e));
218219
logger.debug(e.toString(), LogValues.exceptionTrace(e));
219-
return builder.valid(false).build();
220+
// Overrides only add to the state. Without them the state is invalid and empty here, so it
221+
// holds the override layer's flags and stays invalid. A client-side SDK bootstrapped from the
222+
// state uses its flags whatever $valid says (js-client-sdk 3.x and the 4.x browser SDK only
223+
// log a warning), so the overridden values still reach it.
224+
Map<String, DataStoreTypes.ItemDescriptor> layerFlags =
225+
overrideLayer == null ? Collections.emptyMap() : overrideLayer.all(FEATURES);
226+
if (layerFlags.isEmpty()) {
227+
return builder.valid(false).build();
228+
}
229+
flags = new DataStoreTypes.KeyedItems<>(layerFlags.entrySet());
230+
valid = false;
220231
}
221232
}
222233

@@ -243,7 +254,7 @@ public FeatureFlagsState allFlagsState(LDContext context, FlagsStateOption... op
243254
.withOverrideAffected(flag.isOverride()));
244255
}
245256
}
246-
return builder.build();
257+
return builder.valid(valid).build();
247258
}
248259

249260
private static DataModel.FeatureFlag getFlag(ReadOnlyStore store, String key) {

‎lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientOverridesTest.java‎

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,28 @@ public void allFlagsStateBeforeInitializationExcludesLaunchDarklyDataFromAStoreW
232232
}
233233

234234
@Test
235-
public void allFlagsStateIsInvalidWhenTheStoreFailsEvenWithOverrides() throws Exception {
236-
// A daemon-mode client whose store holds an init marker but cannot list its flags reports an
237-
// invalid state and logs the failure, as it does without a source, rather than a valid state that
238-
// holds only the overrides.
235+
public void allFlagsStateWhenTheStoreFailsHoldsTheOverridesAndIsInvalid() throws Exception {
236+
// A daemon-mode client whose store holds an init marker but cannot list its flags logs the
237+
// failure. Without a source the state is invalid and empty; the overrides add to it, so it holds
238+
// the overridden flags and stays invalid.
239239
FixedDataStore store = new FixedDataStore(flagsOnly(), true);
240240
store.getAllError = new RuntimeException("store failed");
241241
TestOverrideSource source = new TestOverrideSource(flagsOnly(valueFlag("overridden", "override-value")));
242-
LDConfig config = configWith(Components.dataSystem().daemon(context -> store).overrides(source))
243-
.startWait(Duration.ZERO)
244-
.build();
245-
try (LDClient client = new LDClient(SDK_KEY, config)) {
242+
try (LDClient client = new LDClient(SDK_KEY, daemonConfig(store, source))) {
243+
FeatureFlagsState state = client.allFlagsState(CONTEXT, FlagsStateOption.WITH_REASONS);
244+
assertFalse(state.isValid());
245+
assertEquals(Collections.singletonMap("overridden", LDValue.of("override-value")), state.toValuesMap());
246+
assertTrue(state.getFlagReason("overridden").isOverrideAffected());
247+
assertTrue(logCapture.getMessageStrings().stream().anyMatch(m ->
248+
m.startsWith("ERROR:Exception from data store when evaluating all flags") && m.contains("store failed")));
249+
}
250+
}
251+
252+
@Test
253+
public void allFlagsStateWhenTheStoreFailsAndTheLayerHoldsNoFlagsIsInvalidAndEmpty() throws Exception {
254+
FixedDataStore store = new FixedDataStore(flagsOnly(), true);
255+
store.getAllError = new RuntimeException("store failed");
256+
try (LDClient client = new LDClient(SDK_KEY, daemonConfig(store, new TestOverrideSource(Collections.emptyList())))) {
246257
FeatureFlagsState state = client.allFlagsState(CONTEXT);
247258
assertFalse(state.isValid());
248259
assertEquals(Collections.emptyMap(), state.toValuesMap());
@@ -251,6 +262,12 @@ public void allFlagsStateIsInvalidWhenTheStoreFailsEvenWithOverrides() throws Ex
251262
}
252263
}
253264

265+
private LDConfig daemonConfig(DataStore store, TestOverrideSource source) {
266+
return configWith(Components.dataSystem().daemon(context -> store).overrides(source))
267+
.startWait(Duration.ZERO)
268+
.build();
269+
}
270+
254271
@Test
255272
public void allFlagsStateTurnsOffEventTrackingForOverrideAffectedFlags() throws Exception {
256273
long debugUntil = System.currentTimeMillis() + 100000;

0 commit comments

Comments
 (0)