Skip to content

Commit 6804e26

Browse files
chore: handles offline mode, no initializers nor synchronizers (#117)
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) - [x] I have validated my changes against all supported platform versions <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes affect initialization and offline data source selection, which can alter client connectivity/status behavior. Persistence propagation tweaks also impact whether updates are written to persistent stores. > > **Overview** > **FDv2 now behaves cleanly in offline and no-source configurations.** `FDv2DataSystem` builds an `externalUpdatesOnly` data source when `config.offline` is true, and `FDv2DataSource` explicitly marks the client `VALID` (with a log) when there are no initializers/synchronizers instead of trying to connect. > > **Persistence signaling is now consistently propagated.** `DataSourceSynchronizerAdapter` includes `shouldPersist` when converting FDv1 `init` payloads into FDv2 `ChangeSet`s and defaults FDv1 `upsert` conversions to persist, while `WriteThroughStore` simplifies/guards persistent-store calls using `hasPersistence`. > > Adds a regression test to ensure an offline client using the FDv2 data system initializes successfully and reports `VALID` status. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6ae877b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com>
1 parent 4cc57ee commit 6804e26

5 files changed

Lines changed: 42 additions & 20 deletions

File tree

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/DataSourceSynchronizerAdapter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ public boolean init(DataStoreTypes.FullDataSet<DataStoreTypes.ItemDescriptor> al
133133
DataStoreTypes.ChangeSetType.Full,
134134
Selector.EMPTY,
135135
allData.getData(),
136-
null);
136+
null,
137+
allData.shouldPersist()
138+
);
137139
resultQueue.put(FDv2SourceResult.changeSet(changeSet, false));
138140
return true;
139141
}
@@ -153,7 +155,9 @@ public boolean upsert(DataStoreTypes.DataKind kind, String key, DataStoreTypes.I
153155
DataStoreTypes.ChangeSetType.Partial,
154156
Selector.EMPTY,
155157
data,
156-
null);
158+
null,
159+
true // default to true as this adapter is used for adapting FDv1 data sources which are always persistent
160+
);
157161
resultQueue.put(FDv2SourceResult.changeSet(changeSet, false));
158162
return true;
159163
}

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/FDv2DataSource.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ private void run() {
117117
if (!sourceManager.hasAvailableSources()) {
118118
// There are not any initializer or synchronizers, so we are at the best state that
119119
// can be achieved.
120+
logger.info("LaunchDarkly client will not connect to Launchdarkly for feature flag data due to no initializers or synchronizers");
120121
dataSourceUpdates.updateStatus(DataSourceStatusProvider.State.VALID, null);
121122
startFuture.complete(true);
122123
return;

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/FDv2DataSystem.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import com.launchdarkly.sdk.server.subsystems.DataStore;
1616
import com.launchdarkly.sdk.server.subsystems.LoggingConfiguration;
1717
import com.launchdarkly.sdk.server.subsystems.DataSystemConfiguration;
18-
import com.launchdarkly.sdk.server.subsystems.ComponentConfigurer;
1918

2019
import java.io.Closeable;
2120
import java.io.IOException;
@@ -167,15 +166,21 @@ static FDv2DataSystem create(
167166
};
168167
}
169168

170-
DataSource dataSource = new FDv2DataSource(
171-
initializerFactories,
172-
synchronizerFactories,
173-
fdv1FallbackFactory,
174-
dataSourceUpdates,
175-
config.threadPriority,
176-
clientContext.getBaseLogger().subLogger(Loggers.DATA_SOURCE_LOGGER_NAME),
177-
clientContext.sharedExecutor
178-
);
169+
final DataSource dataSource;
170+
if (config.offline) {
171+
dataSource = Components.externalUpdatesOnly().build(clientContext.withDataSourceUpdateSink(dataSourceUpdates));
172+
} else {
173+
dataSource = new FDv2DataSource(
174+
initializerFactories,
175+
synchronizerFactories,
176+
fdv1FallbackFactory,
177+
dataSourceUpdates,
178+
config.threadPriority,
179+
clientContext.getBaseLogger().subLogger(Loggers.DATA_SOURCE_LOGGER_NAME),
180+
clientContext.sharedExecutor
181+
);
182+
}
183+
179184
DataSourceStatusProvider dataSourceStatusProvider = new DataSourceStatusProviderImpl(
180185
dataSourceStatusBroadcaster,
181186
dataSourceUpdates);

lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/WriteThroughStore.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,8 @@ public void init(FullDataSet<ItemDescriptor> allData) {
5858
maybeSwitchStore();
5959

6060
// Only write to persistent store if shouldPersist is true and store is in READ_WRITE mode
61-
if (persistenceMode == DataStoreMode.READ_WRITE && allData.shouldPersist()) {
62-
if (persistentStore != null) {
63-
persistentStore.init(allData);
64-
}
61+
if (hasPersistence && persistenceMode == DataStoreMode.READ_WRITE && allData.shouldPersist()) {
62+
persistentStore.init(allData);
6563
}
6664
}
6765

@@ -97,12 +95,12 @@ public boolean isInitialized() {
9795

9896
@Override
9997
public boolean isStatusMonitoringEnabled() {
100-
return persistentStore != null ? persistentStore.isStatusMonitoringEnabled() : false;
98+
return hasPersistence ? persistentStore.isStatusMonitoringEnabled() : false;
10199
}
102100

103101
@Override
104102
public CacheStats getCacheStats() {
105-
return persistentStore != null ? persistentStore.getCacheStats() : null;
103+
return hasPersistence ? persistentStore.getCacheStats() : null;
106104
}
107105

108106
@Override
@@ -135,7 +133,7 @@ public Selector getSelector() {
135133
@Override
136134
public void close() throws IOException {
137135
memoryStore.close();
138-
if (persistentStore != null) {
136+
if (hasPersistence) {
139137
persistentStore.close();
140138
}
141139
}

lib/sdk/server/src/test/java/com/launchdarkly/sdk/server/LDClientOfflineTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.collect.ImmutableMap;
44
import com.launchdarkly.sdk.LDContext;
55
import com.launchdarkly.sdk.LDValue;
6+
import com.launchdarkly.sdk.server.integrations.DataSystemModes;
67
import com.launchdarkly.sdk.server.interfaces.DataSourceStatusProvider;
78
import com.launchdarkly.sdk.server.subsystems.DataStore;
89

@@ -78,4 +79,17 @@ public void offlineClientGetsFlagsStateFromDataStore() throws IOException {
7879
assertEquals(ImmutableMap.<String, LDValue>of("key", LDValue.of(true)), state.toValuesMap());
7980
}
8081
}
81-
}
82+
83+
@Test
84+
public void offlineWithFDv2DataSystemIsInitializedAndReportsValidStatus() throws IOException {
85+
LDConfig config = baseConfig()
86+
.dataSystem(new DataSystemModes().defaultMode())
87+
.offline(true)
88+
.build();
89+
try (LDClient client = new LDClient("SDK_KEY", config)) {
90+
assertTrue(client.dataSystem instanceof FDv2DataSystem);
91+
assertTrue(client.isInitialized());
92+
assertEquals(DataSourceStatusProvider.State.VALID, client.getDataSourceStatusProvider().getStatus().getState());
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)