Skip to content

chore: Mode resolution and switching for FDv2#326

Merged
aaron-zeisler merged 18 commits intomainfrom
aaronz/SDK-1956/mode-resolution-and-switching
Mar 27, 2026
Merged

chore: Mode resolution and switching for FDv2#326
aaron-zeisler merged 18 commits intomainfrom
aaronz/SDK-1956/mode-resolution-and-switching

Conversation

@aaron-zeisler
Copy link
Copy Markdown
Contributor

@aaron-zeisler aaron-zeisler commented Mar 11, 2026

Details coming soon

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

Note

Medium Risk
Introduces a new FDv2 mode-resolution and rebuild path in ConnectivityManager, which changes when data sources are torn down/recreated across foreground/offline/network transitions. Risk is mitigated by substantial new unit coverage, but regressions could impact connection lifecycle and initialization timing.

Overview
Adds an internal FDv2 connection mode system (ConnectionMode, ModeState, ModeResolutionTable, ModeDefinition) and a new FDv2DataSourceBuilder that builds an FDv2DataSource based on the resolved mode.

Updates ConnectivityManager to snapshot platform state, update the event processor consistently, and (when using FDv2DataSourceBuilder) resolve modes and rebuild data sources only on meaningful mode changes (including an equivalence check to skip rebuilds when configs match); it also closes the factory on shutdown when supported.

Extends ClientContextImpl to optionally carry a TransactionalDataStore through forDataSource, and updates FDv2DataSource.needsRefresh to rebuild only on context changes. Adds FDv2 endpoint base paths in StandardEndpoints and broad new tests covering FDv1 regression scenarios plus FDv2 mode switching/resolution behavior.

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

Base automatically changed from ta/SDK-1835/initializers-synchronizers to main March 13, 2026 17:18
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from 4e24bb9 to 070f859 Compare March 17, 2026 21:44
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from ee7bfc4 to c71379b Compare March 17, 2026 23:29
@aaron-zeisler aaron-zeisler changed the title Aaronz/sdk 1956/mode resolution and switching chore: Mode resolution and switching for FDv2 Mar 17, 2026
@aaron-zeisler
Copy link
Copy Markdown
Contributor Author

I merged the code from the other approach into this branch in the commit titled "refactor: switch to Approach 2 for FDv2 mode resolution and switching", and then I closed that other PR.

@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from c71379b to 0b070e6 Compare March 19, 2026 21:59
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch 2 times, most recently from b0b1e2f to ba6ac91 Compare March 20, 2026 20:26
@aaron-zeisler aaron-zeisler marked this pull request as ready for review March 20, 2026 20:26
@aaron-zeisler aaron-zeisler requested a review from a team as a code owner March 20, 2026 20:26
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from ba6ac91 to 34c2752 Compare March 23, 2026 03:41
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from 34c2752 to 1c78c34 Compare March 23, 2026 04:00
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from 1c78c34 to 5c6ac49 Compare March 23, 2026 18:39
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from 6fcd7ff to e82d3da Compare March 25, 2026 16:34
Introduces the core types for FDv2 mode resolution (CONNMODE spec):
- ConnectionMode: enum for streaming, polling, offline, one-shot, background
- ModeDefinition: initializer/synchronizer lists per mode with stubbed configurers
- ModeState: platform state snapshot (foreground, networkAvailable)
- ModeResolutionEntry: condition + mode pair for resolution table entries
- ModeResolutionTable: ordered first-match-wins resolver with MOBILE default table
- ModeAware: interface for DataSources that support runtime switchMode()

All types are package-private. No changes to existing code.
Add ResolvedModeDefinition and mode-table constructors so
FDv2DataSource can look up initializer/synchronizer factories per
ConnectionMode. switchMode() tears down the current SourceManager,
builds a new one with the target mode's synchronizers (skipping
initializers per CONNMODE 2.0.1), and schedules execution on the
background executor. runSynchronizers() now takes an explicit
SourceManager parameter to prevent a race where the finally block
could close a SourceManager swapped in by a concurrent switchMode().
Introduces FDv2DataSourceBuilder, a ComponentConfigurer<DataSource> that
resolves the ModeDefinition table's ComponentConfigurers into
DataSourceFactories at build time by capturing the ClientContext. The
configurers are currently stubbed (return null); real wiring of concrete
initializer/synchronizer types will follow in a subsequent commit.
…ourceBuilder

Replace stub configurers with concrete factories that create
FDv2PollingInitializer, FDv2PollingSynchronizer, and
FDv2StreamingSynchronizer. Shared dependencies (SelectorSource,
ScheduledExecutorService) are created once per build() call; each
factory creates a fresh DefaultFDv2Requestor for lifecycle isolation.

Add FDv2 endpoint path constants to StandardEndpoints. Thread
TransactionalDataStore through ClientContextImpl and ConnectivityManager
so the builder can construct SelectorSourceFacade from ClientContext.
ConnectivityManager now detects ModeAware data sources and routes
foreground, connectivity, and force-offline state changes through
resolveAndSwitchMode() instead of the legacy teardown/rebuild cycle.
…d switching

Replace Approach 1 implementation with Approach 2, which the team
preferred for its cleaner architecture:

- ConnectivityManager owns the resolved mode table and performs
  ModeState -> ConnectionMode -> ResolvedModeDefinition lookup
- FDv2DataSource receives ResolvedModeDefinition via switchMode()
  and has no internal mode table
- FDv2DataSourceBuilder uses a unified ComponentConfigurer-based
  code path for both production and test mode tables
- ResolvedModeDefinition is a top-level class rather than an inner
  class of FDv2DataSource
- ConnectionMode is a final class with static instances instead of
  a Java enum

Made-with: Cursor
FDv2DataSource now explicitly implements both DataSource and ModeAware,
keeping the two interfaces independent.

Made-with: Cursor
…n ConnectivityManager

Extract updateEventProcessor() and handleModeStateChange() so that
event processor state (setOffline, setInBackground) is managed
independently from data source lifecycle. Both platform listeners
and setForceOffline() now route through handleModeStateChange(),
which snapshots state once and updates each subsystem separately.

Made-with: Cursor
…o prevent race condition

SourceManager now owns a switchSynchronizers() method that atomically
swaps the synchronizer list under the existing lock, eliminating the
window where two runSynchronizers() loops could push data into the
update sink concurrently. FDv2DataSource keeps a single final
SourceManager and uses an AtomicBoolean guard to ensure only one
execution loop runs at a time.

Made-with: Cursor
…pdateDataSource

handleModeStateChange() now simply updates the event processor and
delegates to updateDataSource(). The FDv2 ModeAware early-return and
FDv1 needsRefresh() check both live inside updateDataSource, keeping
the branching logic in one place.

Made-with: Cursor
…nectivityManager level

Per updated CSFDV2 spec and JS implementation, mode switching now tears
down the old data source and builds a new one rather than swapping
internal synchronizers. Delete ModeAware interface, remove switchMode()
from FDv2DataSource and switchSynchronizers() from SourceManager.
FDv2DataSourceBuilder becomes the sole owner of mode resolution via
setActiveMode()/build(), with ConnectivityManager using a
useFDv2ModeResolution flag to route FDv2 through the new path while
preserving FDv1 behavior. Implements CSFDV2 5.3.8 (retain data source
when old and new modes share the same ModeDefinition).

Made-with: Cursor
- Short-circuit forceOffline in resolveMode() so ModeState reflects actual platform state
- Match ConnectionMode string values to cross-SDK spec (lowercase, hyphenated)
- Add Javadoc to ConnectionMode, ClientContextImpl overloads, and FDv2DataSource internals
- Inline FDv2DataSourceBuilder casts in ConnectivityManager
- Restore try/finally and explanatory comments in runSynchronizers

Made-with: Cursor
- Extract DataSourceSetup helper and makePollingRequestor() to eliminate
  duplicated configurer boilerplate in FDv2DataSourceBuilder
- Share builder's sharedExecutor across all components to prevent thread
  pool leaks on mode switches; increase pool size from 2 to 4
- Make FDv2DataSourceBuilder implement Closeable; shut down executor in
  ConnectivityManager.shutDown()
- Honor backgroundUpdatingDisabled in FDv2 mode resolution by adding the
  flag to ModeState and ModeResolutionTable

Made-with: Cursor
- ConnectivityManager: use .equals() for ModeDefinition comparison,
  synchronize handleModeStateChange()
- FDv2DataSourceBuilder: remove redundant unmodifiableMap() wrapping
- ModeResolutionTable: use explicit default mode instead of catch-all entry
- Add 5 FDv1 round-trip tests for offline, network, and foreground transitions
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch from e82d3da to ee8ce29 Compare March 25, 2026 16:38
@aaron-zeisler
Copy link
Copy Markdown
Contributor Author

Apologies again @tanderson-ld , but I rebased with the main branch and then pushed. This whole thing is going to be squashed (hopefully today), which will probably make for a challenging merge in your development branch.

() -> workerPool.execute(task),
initialDelayMillis, TimeUnit.MILLISECONDS);
return task;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider just having a pooled scheduled executor for long running work. We shouldn't need to make the RepeatingTask and should be able to use existing java paradigms.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After more investigation and discussion, we're going to stick with the FDv2DataSourceFactory owning the fixed thread pool executor. These changes should be reverted.

Move resolveMode() into updateDataSource so it covers both startup and
state-change paths, eliminating the duplicate call in startUp that was
always overwritten.

Made-with: Cursor
@aaron-zeisler aaron-zeisler force-pushed the aaronz/SDK-1956/mode-resolution-and-switching branch 2 times, most recently from ca50abd to 9fad9ac Compare March 25, 2026 21:03
@tanderson-ld tanderson-ld self-requested a review March 25, 2026 21:04
Copy link
Copy Markdown
Contributor

@tanderson-ld tanderson-ld left a comment

Choose a reason for hiding this comment

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

Final bench testing.

…consistency

Guard needsRefresh() so it only fires when platform state has changed
since the last data source build, preventing unnecessary FDv1 polling
rebuilds on connectivity events. Snapshot ModeState once per state change
and pass it through updateDataSource to eliminate race between event
processor and data source reads. Synchronize switchToContext. Add
equals/hashCode to ModeState.

Made-with: Cursor
Used the Android Studio to geenrate it instead of Cursor AI
Copy link
Copy Markdown

@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 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

@NonNull
ConnectionMode getStartingMode() {
return startingMode;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused getStartingMode method in FDv2DataSourceBuilder

Low Severity

getStartingMode() is defined but never called anywhere in the codebase. A grep for getStartingMode only returns its definition. The startingMode field is used internally via activeMode != null ? activeMode : startingMode in build(), but the getter itself is dead code.

Fix in Cursor Fix in Web

@aaron-zeisler aaron-zeisler merged commit f7acc8f into main Mar 27, 2026
6 of 7 checks passed
@aaron-zeisler aaron-zeisler deleted the aaronz/SDK-1956/mode-resolution-and-switching branch March 27, 2026 21:33
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.

2 participants