Skip to content

Commit

Permalink
fix: registered LDStatusListeners are now sent updates that result fr…
Browse files Browse the repository at this point in the history
…om a call to LDClient.identify(...).
  • Loading branch information
tanderson-ld committed Jul 8, 2024
1 parent 5a0fd0e commit cf9ece6
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ class ConnectivityManager {
// source we're using, like "if there was an error, update the ConnectionInformation."
private class DataSourceUpdateSinkImpl implements DataSourceUpdateSink {
private final ContextDataManager contextDataManager;
private final AtomicReference<ConnectionMode> connectionMode = new AtomicReference<>(null);
private final AtomicReference<LDFailure> lastFailure = new AtomicReference<>(null);

DataSourceUpdateSinkImpl(ContextDataManager contextDataManager) {
this.contextDataManager = contextDataManager;
Expand All @@ -97,40 +95,10 @@ public void upsert(LDContext context, DataModel.Flag item) {

@Override
public void setStatus(ConnectionMode newConnectionMode, Throwable error) {
ConnectionMode oldConnectionMode = newConnectionMode == null ? null :
connectionMode.getAndSet(newConnectionMode);
LDFailure failure = null;
if (error != null) {
if (error instanceof LDFailure) {
failure = (LDFailure)error;
} else {
failure = new LDFailure("Unknown failure", error, LDFailure.FailureType.UNKNOWN_ERROR);
}
lastFailure.set(failure);
}
boolean updated = false;
if (newConnectionMode != null && oldConnectionMode != newConnectionMode) {
if (failure == null && newConnectionMode.isConnectionActive()) {
connectionInformation.setLastSuccessfulConnection(System.currentTimeMillis());
}
connectionInformation.setConnectionMode(newConnectionMode);
updated = true;
}
if (failure != null) {
connectionInformation.setLastFailedConnection(System.currentTimeMillis());
connectionInformation.setLastFailure(failure);
updated = true;
}
if (updated) {
try {
saveConnectionInformation();
} catch (Exception ex) {
LDUtil.logExceptionAtErrorLevel(logger, ex, "Error saving connection information");
}
updateStatusListeners(connectionInformation);
if (failure != null) {
updateListenersOnFailure(failure);
}
if (error == null) {
updateConnectionInfoForSuccess(newConnectionMode);
} else {
updateConnectionInfoForError(newConnectionMode, error);
}
}

Expand Down Expand Up @@ -263,11 +231,17 @@ private synchronized boolean updateDataSource(
@Override
public void onSuccess(Boolean result) {
initialized = true;
// passing the current connection mode since we don't want to change the mode, just trigger
// the logic to update the last connection success.
updateConnectionInfoForSuccess(connectionInformation.getConnectionMode());
onCompletion.onSuccess(null);
}

@Override
public void onError(Throwable error) {
// passing the current connection mode since we don't want to change the mode, just trigger
// the logic to update the last connection failure.
updateConnectionInfoForError(connectionInformation.getConnectionMode(), error);
onCompletion.onSuccess(null);
}
});
Expand Down Expand Up @@ -303,6 +277,52 @@ void unregisterStatusListener(LDStatusListener LDStatusListener) {
}
}

private void updateConnectionInfoForSuccess(ConnectionMode connectionMode) {
boolean updated = false;
if (connectionInformation.getConnectionMode() != connectionMode) {
connectionInformation.setConnectionMode(connectionMode);
updated = true;
}

// even if connection mode doesn't change, it may be the case that the data source re-established its connection
// and so we should update the last successful connection time (e.g. connection drops and we reconnect,
// an identify occurs)
if (connectionMode.isConnectionActive()) {
connectionInformation.setLastSuccessfulConnection(System.currentTimeMillis());
updated = true;
}

if (updated) {
try {
saveConnectionInformation(connectionInformation);
} catch (Exception ex) {
LDUtil.logExceptionAtErrorLevel(logger, ex, "Error saving connection information");
}
updateStatusListeners(connectionInformation);
}
}

private void updateConnectionInfoForError(ConnectionMode connectionMode, Throwable error) {
LDFailure failure = null;
if (error != null) {
if (error instanceof LDFailure) {
failure = (LDFailure)error;
} else {
failure = new LDFailure("Unknown failure", error, LDFailure.FailureType.UNKNOWN_ERROR);
}
}

connectionInformation.setConnectionMode(connectionMode);
connectionInformation.setLastFailedConnection(System.currentTimeMillis());
connectionInformation.setLastFailure(failure);
try {
saveConnectionInformation(connectionInformation);
} catch (Exception ex) {
LDUtil.logExceptionAtErrorLevel(logger, ex, "Error saving connection information");
}
updateStatusListeners(connectionInformation);
}

private void readStoredConnectionState() {
PersistentDataStoreWrapper.SavedConnectionInfo savedConnectionInfo =
environmentStore.getConnectionInfo();
Expand All @@ -315,7 +335,7 @@ private void readStoredConnectionState() {
connectionInformation.setLastFailure(savedConnectionInfo.lastFailure);
}

private synchronized void saveConnectionInformation() {
private synchronized void saveConnectionInformation(ConnectionInformation connectionInformation) {
PersistentDataStoreWrapper.SavedConnectionInfo savedConnectionInfo =
new PersistentDataStoreWrapper.SavedConnectionInfo(
connectionInformation.getLastSuccessfulConnection(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ public class ConnectivityManagerTest extends EasyMockSupport {
private static final EnvironmentData DATA = new DataSetBuilder()
.add("flag1", 1, LDValue.of(true), 0)
.build();
private static final ConnectionMode EXPECTED_FOREGROUND_MODE = ConnectionMode.POLLING;
private static final ConnectionMode EXPECTED_BACKGROUND_MODE = ConnectionMode.BACKGROUND_POLLING;

@Rule
public LogCaptureRule logging = new LogCaptureRule();
Expand Down Expand Up @@ -241,7 +239,7 @@ public void initForegroundDataSource() throws Exception {
assertTrue(connectivityManager.isInitialized());
assertFalse(connectivityManager.isForcedOffline());

assertEquals(EXPECTED_FOREGROUND_MODE, connectivityManager.getConnectionInformation().getConnectionMode());
assertEquals(ConnectionMode.POLLING, connectivityManager.getConnectionInformation().getConnectionMode());
assertNull(connectivityManager.getConnectionInformation().getLastFailure());
assertNull(connectivityManager.getConnectionInformation().getLastFailedConnection());
assertNotNull(connectivityManager.getConnectionInformation().getLastSuccessfulConnection());
Expand All @@ -266,7 +264,7 @@ public void initBackgroundDataSource() throws Exception {
assertTrue(connectivityManager.isInitialized());
assertFalse(connectivityManager.isForcedOffline());

assertEquals(EXPECTED_BACKGROUND_MODE, connectivityManager.getConnectionInformation().getConnectionMode());
assertEquals(ConnectionMode.BACKGROUND_POLLING, connectivityManager.getConnectionInformation().getConnectionMode());
assertNull(connectivityManager.getConnectionInformation().getLastFailure());
assertNull(connectivityManager.getConnectionInformation().getLastFailedConnection());
assertNotNull(connectivityManager.getConnectionInformation().getLastSuccessfulConnection());
Expand All @@ -284,7 +282,7 @@ public void initDataSourceWithKnownError() throws ExecutionException {
final Throwable testError = new Throwable();
final LDFailure testFailure = new LDFailure("failure", testError, LDFailure.FailureType.NETWORK_FAILURE);
ComponentConfigurer<DataSource> dataSourceConfigurer = clientContext ->
MockComponents.failingDataSource(clientContext, EXPECTED_FOREGROUND_MODE, testFailure);
MockComponents.failingDataSource(clientContext, ConnectionMode.POLLING, testFailure);

createTestManager(false, false, dataSourceConfigurer);

Expand All @@ -293,7 +291,7 @@ public void initDataSourceWithKnownError() throws ExecutionException {

assertFalse(connectivityManager.isInitialized());
assertFalse(connectivityManager.isForcedOffline());
assertEquals(EXPECTED_FOREGROUND_MODE, connectivityManager.getConnectionInformation().getConnectionMode());
assertEquals(ConnectionMode.POLLING, connectivityManager.getConnectionInformation().getConnectionMode());
LDFailure failure = connectivityManager.getConnectionInformation().getLastFailure();
assertNotNull(failure);
assertEquals("failure", failure.getMessage());
Expand All @@ -311,7 +309,7 @@ public void initDataSourceWithUnknownError() throws ExecutionException {

final Throwable testError = new Throwable();
ComponentConfigurer<DataSource> dataSourceConfigurer = clientContext ->
MockComponents.failingDataSource(clientContext, EXPECTED_FOREGROUND_MODE, testError);
MockComponents.failingDataSource(clientContext, ConnectionMode.POLLING, testError);

createTestManager(false, false, dataSourceConfigurer);

Expand Down Expand Up @@ -342,7 +340,7 @@ public void setOfflineAfterInit() throws Exception {

assertTrue(connectivityManager.isInitialized());
assertFalse(connectivityManager.isForcedOffline());
assertEquals(EXPECTED_FOREGROUND_MODE, connectivityManager.getConnectionInformation().getConnectionMode());
assertEquals(ConnectionMode.POLLING, connectivityManager.getConnectionInformation().getConnectionMode());

verifyForegroundDataSourceWasCreatedAndStarted(CONTEXT);
verifyNoMoreDataSourcesWereCreated();
Expand All @@ -356,7 +354,7 @@ public void setOfflineAfterInit() throws Exception {

// We don't currently have a good way to wait for this state change to take effect, so we'll
// poll for it.
ConnectionMode newConnectionMode = awaitConnectionModeChangedFrom(EXPECTED_FOREGROUND_MODE);
ConnectionMode newConnectionMode = awaitConnectionModeChangedFrom(ConnectionMode.POLLING);
assertEquals(ConnectionMode.SET_OFFLINE, newConnectionMode);
assertTrue(connectivityManager.isForcedOffline());

Expand Down Expand Up @@ -566,7 +564,7 @@ private ComponentConfigurer<DataSource> makeSuccessfulDataSourceFactory() {
private DataSource makeSuccessfulDataSource(ClientContext clientContext) {
receivedClientContexts.add(clientContext);
return MockComponents.successfulDataSource(clientContext, DATA,
clientContext.isInBackground() ? EXPECTED_BACKGROUND_MODE : EXPECTED_FOREGROUND_MODE,
clientContext.isInBackground() ? ConnectionMode.BACKGROUND_POLLING : ConnectionMode.POLLING,
startedDataSources,
stoppedDataSources);
}
Expand Down

0 comments on commit cf9ece6

Please sign in to comment.