Skip to content

chore: transactional ContextDataManager in prep for FDv2#322

Open
tanderson-ld wants to merge 2 commits intomainfrom
ta/SDK-1817/composite-source-pt1
Open

chore: transactional ContextDataManager in prep for FDv2#322
tanderson-ld wants to merge 2 commits intomainfrom
ta/SDK-1817/composite-source-pt1

Conversation

@tanderson-ld
Copy link
Contributor

@tanderson-ld tanderson-ld commented Feb 18, 2026

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
SDK-1817


Note

Medium Risk
Touches core flag update/persistence and listener-notification paths and adds a new update mechanism that could affect correctness under concurrency/versioning; mitigated by focused unit tests but still impacts critical runtime behavior.

Overview
Adds a transactional update API for flag data by introducing ChangeSet/ChangeSetType plus TransactionalDataStore/TransactionalDataSourceUpdateSink, and extends the data source sink surface with DataSourceUpdateSinkV2.

Refactors ContextDataManager to implement transactional apply() for full replace and partial merge updates (including optional persistence) and to track the latest non-empty Selector in memory; ConnectivityManager now forwards apply(ChangeSet) from the sink to the store.

Updates shared test infrastructure and adds ContextDataManagerApplyTest to cover full/partial/none changesets, persistence toggles, selector retention behavior, and context-mismatch no-ops; shared test module now depends on launchdarkly-java-sdk-internal for Selector.

Written by Cursor Bugbot for commit 1e3da90. This will update automatically on new commits. Configure here.

@tanderson-ld tanderson-ld marked this pull request as ready for review February 19, 2026 14:33
@tanderson-ld tanderson-ld requested a review from a team as a code owner February 19, 2026 14:33
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

break;
case None:
default:
break;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None changeset silently drops its selector

Medium Severity

The ChangeSetType.None documentation explicitly states the changeset "may still carry a selector to store in memory," but the apply method's None case just breaks without updating currentSelector. A None changeset with a non-empty selector will have that selector silently discarded. Both Full and Partial branches update the selector, but None does not.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this matters as the selector should be the same as the previous selector.

I guess theoretically the back-end could just keep sending us different selectors, but it shouldn't have a practical effect.

logger.debug("Using stored flag data for this context");
initDataInternal(context, storedData, false);
// when we switch context, we don't have a selector because we don't currently support persisting the selector.
applyFullData(context, Selector.EMPTY, storedData.getAll(), false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale selector persists after context switch

Medium Severity

When switchToContext is called, currentSelector is never reset. The comment at line 106 states the intent is to have no selector after switching, but applyFullData is called with Selector.EMPTY, and the if (!selector.isEmpty()) guard prevents the assignment. Additionally, when storedData is null, the method returns early without any selector reset. This leaves a stale selector from the previous context, which could cause incorrect basis requests in FDv2.

Additional Locations (1)

Fix in Cursor Fix in Web

@NonNull private volatile ContextIndex index;

/** Selector from the last applied changeset that carried one; in-memory only, not persisted. */
@NonNull private volatile Selector currentSelector = Selector.EMPTY;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this works out because we are single-threaded single-producer for that data. But the volatile sometimes makes me unseasy, because it feels like the writes should by synced. No action required, just chain of thought.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writes are synchronized. So I am happy.

if (!context.equals(currentContext)) {
return;
}
if (!selector.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be non-conditional.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are applying selectorless data, then data would need to resume based on a selectorless state. Otherwise we would apply a patch with unrelated provenance.

* to store in memory for the next request (e.g. as basis), and for Full/Partial types a map of
* flag key to flag data.
*/
public final class ChangeSet {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this compatible with the server one? I am just wondering if maybe we can hoist the server one because it is generic over the model type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments