-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: improved LDClient.identify(...) behavior when offline #261
Conversation
This pull request has been linked to Shortcut Story #237349: Investigate Android airplane mode identify bug. |
application.unregisterReceiver(connectivityReceiver); | ||
} catch (IllegalArgumentException e) { | ||
// getting here just means the receiver wasn't registered, which is our objective | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: AndroidPlatformState claims to be closeable, in which close is idempotent after the first call. This changes makes it idempotent.
this.platformState, | ||
this.taskExecutor | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: LDClient's ClientContextImpl member variable should be the authority on the current context. This allows us to change the context in that object so that can be true after an identify call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some concerns with this. Copies of the client context are passed to various components, and those client contexts do not have a centralized update mechanism. So putting the evaluation context into the client context provides easy access to a stale or unset LDContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. I believe that is how it has been. Any components that use the context have an alternative mechanism for updating it when it changes.
@@ -76,19 +75,23 @@ class ConnectivityManager { | |||
// data is stored; 2. to implement additional logic that does not depend on what kind of data | |||
// source we're using, like "if there was an error, update the ConnectionInformation." | |||
private class DataSourceUpdateSinkImpl implements DataSourceUpdateSink { | |||
private final ContextDataManager contextDataManager; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: The sink now holds the reference to the data manager instead of a private class holding a reference to an enclosing class's member.
contextDataManager.initData(currentEvaluationContext.get(), | ||
EnvironmentData.usingExistingFlagsMap(items)); | ||
public void init(LDContext context, Map<String, DataModel.Flag> items) { | ||
contextDataManager.initData(context, EnvironmentData.usingExistingFlagsMap(items)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: The context was added to go along with the flag data. This helps other parts of the system make decisions on whether or not to accept the flag data. Ideally, this manager and the sources of this data could only exist when getting data from them makes sense, but a refactoring of the managers to be instead immutable instances and implement disposable is too risky at this time.
// this context. If they don't, it has no effect. Currently we do *not* return early from | ||
// initialization just because stored flags exist; we're just making them available in case | ||
// initialization times out or otherwise fails. | ||
contextDataManager.initFromStoredData(currentEvaluationContext.get()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: The ConnectivityManager should not be involved at all with the concept of using cached data.
environmentStore.setContextData(contextId, newData); | ||
logger.debug("Updated flag data for context {} in persistent store", contextId); | ||
} | ||
environmentStore.setIndex(newIndex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: environmentStore.setIndex(newIndex);
should be in the lock block, so it was moved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the long-term I think we should attempt to change this to a queue of operations that apply to the store. Maybe transactional in some way. It would be preferable to not have to lock for the duration of IO.
@@ -162,21 +139,21 @@ private void initDataInternal( | |||
newIndex = index.updateTimestamp(contextId, System.currentTimeMillis()) | |||
.prune(maxCachedContexts, removedContextIds); | |||
index = newIndex; | |||
flagsContextId = contextId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: Calculating a context id is fast and trivial. Why risk it being outdated? Removed.
* @param context the new context | ||
* @return true if successful | ||
*/ | ||
public boolean initFromStoredData(@NonNull LDContext context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: This functionality was moved to switchToContext. This also helps to privatize the concept of stored data so that other components don't have to be involved. Yay separation of concerns.
} | ||
|
||
/** | ||
* Returns the current context. | ||
* Switches to providing flag data for the provided context. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: You can no longer switch which context this DataManager is concerned with without also attempting to switch the flag data. Weird that was ever an option to begin with...
if (!context.equals(currentContext)) { | ||
// if incoming new data is not for the current context, reject it. | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reviewers: I considered refactoring this code to be willing to store the data for the non-current context if such a situation was to occur, but it seemed unlikely and the refactor would be with risk. There is also an interesting edge case / race condition if that non-current context was not already cached and it put the cache over its cacheLimit.
// Calling initFromStoredData updates the current flag state *if* stored flags exist for | ||
// this context. If they don't, it has no effect. Currently we do *not* return early from | ||
// initialization just because stored flags exist; we're just making them available in case | ||
// initialization times out or otherwise fails. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to remove copy-pasta'd comment.
Need to add unit test and end to end integration test. |
...d-client-sdk/src/test/java/com/launchdarkly/sdk/android/ContextDataManagerListenersTest.java
Outdated
Show resolved
Hide resolved
launchdarkly-android-client-sdk/src/main/java/com/launchdarkly/sdk/android/LDClient.java
Outdated
Show resolved
Hide resolved
...droid-client-sdk/src/androidTest/java/com/launchdarkly/sdk/android/LDClientEndToEndTest.java
Show resolved
Hide resolved
} | ||
EnvironmentData storedData = getStoredData(currentContext); | ||
if (storedData == null) { | ||
logger.debug("No stored flag data is available for this context"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay logging.
🤖 I have created a release *beep* *boop* --- ## [5.1.1](5.1.0...5.1.1) (2024-03-26) ### Bug Fixes * improved LDClient.identify(...) behavior when offline ([#261](#261)) ([24ce942](24ce942)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Requirements
I have added test coverage for new or changed functionality
I have followed the repository's pull request submission guidelines
I have validated my changes against all supported platform versions
Related issues
SC-237349
Describe the solution you've provided
Updated code so that ContextDataManager switches context independently of ConnectivityManager and ConnectivityManager is not in the path of context switching.