feat: Add async FDv2 data system - #486
Conversation
027941e to
b911aa7
Compare
b911aa7 to
35dd8be
Compare
| :param store_writable: Whether the persistent store should be written to | ||
| :param disabled: Whether the data system is disabled (offline mode) | ||
| """ | ||
| super().__init__(config, data_system_config) |
There was a problem hiding this comment.
Sync store I/O blocks event loop
Medium Severity
AsyncFDv2 reuses _FDv2Base, which attaches a synchronous FeatureStore as the active store and persists via sync Store.apply / commit. Those calls run on the asyncio event loop, so a configured persistent store can stall flag evaluation and data updates for the whole async client until the I/O finishes.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 35dd8be. Configure here.
35dd8be to
c88b6aa
Compare
| return self._store.get(kind, key, lambda x: x) | ||
|
|
||
| async def all(self, kind): | ||
| return self._store.all(kind, lambda x: x) |
There was a problem hiding this comment.
Store view skips flag model decode
Medium Severity
_AsyncStoreView returns store items as-is, but get_active_store() can be a persistent FeatureStore that yields raw dicts. The sync client decodes those via _get_store_item before evaluation; the async client feeds store.get / store.all straight into AsyncEvaluator. With a populated persistent store (cached data before network init), evaluations can fail or misbehave because they receive dicts instead of FeatureFlag / segment models.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c88b6aa. Configure here.
c88b6aa to
1c996d6
Compare
1c996d6 to
9f6ce38
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 938eff1. Configure here.
| if sync_reader is not None: | ||
| sync_reader.cancel() | ||
|
|
||
| await synchronizer.stop() |
There was a problem hiding this comment.
Reader cancel races synchronizer cleanup
Medium Severity
sync_reader.cancel() runs and then synchronizer.stop() is awaited without waiting for the cancelled reader to finish unwinding first. That races sync() generator cleanup against stop(). For AsyncPollingDataSource, stop() only signals events and relies on sync()'s finally to close the requester, so an aborted cleanup can leak the polling HTTP transport—especially on Python 3.11+, where a pending cancellation re-interrupts awaits inside finally/aclose. Elsewhere, FDv1 async streaming cancels and waits before teardown.
Reviewed by Cursor Bugbot for commit 938eff1. Configure here.
938eff1 to
cde1b0b
Compare
cde1b0b to
ef7c261
Compare


Overview
Part of the async Python SDK work (epic SDK-60). This is the second of two stacked PRs
that add async FDv2 support. It is stacked on #485 (async FDv2 data sources) and
adds the async FDv2 data system (coordinator) and wires it into the async client.
This is experimental and should not be considered production-ready.
What this PR adds
impl/datasystem/async_fdv2.py—AsyncFDv2, the async data system that coordinatesthe async initializers and synchronizers, mirrors the sync
FDv2fallback/recoverybehavior, and exposes the async data source status and flag tracking.
async_client.pywiring:_make_data_systemnow buildsAsyncFDv2instead of raisingNotImplementedError._wire_data_source_sessionsshares the client's aiohttp session into async datasource builders so the sources reuse the client's connection pool.
__start_upcalls the wiring before start when a data system config is present andthe client is not offline.
Shared refactor
impl/datasystem/fdv2_common.pygainsfallback_conditionandrecovery_conditionas module-level functions, moved out of the sync
FDv2methods so both the sync andasync data systems share one implementation. Sync
fdv2.pynow calls them.Testing
LD_SKIP_DATABASE_TESTS=1 uv run pytest ldclient/testing/impl/datasystem/— 89 passed.ldclient/testing/test_async_client.py— 21 passed.make lint(mypy, isort, pycodestyle) — clean.Tracked internally: SDK-2870
Note
Overview
Adds async FDv2 so the async Python client can use Flag Delivery V2 when
datasystem_configis set, instead of raisingNotImplementedError.AsyncFDv2coordinates async initializers and synchronizers (main loop, initializer chain, synchronizer fallback/recovery/FDv1 handoff) and exposes flag data via_AsyncStoreViewon the shared in-memory store.async_clientbuildsAsyncFDv2from_make_data_system, and_wire_data_source_sessionsinjects the client’s shared aiohttp session into async polling/streaming builders before start so FDv2 sources reuse the connection pool.Refactor: sync and async FDv2 share
_FDv2Baseinfdv2_common(store, status providers, persistence, data availability) plusfallback_condition/recovery_conditionmoved out of syncFDv2. Syncfdv2.pydelegates to the base and shared helpers.New
test_async_fdv2.pycovers start/stop, updates, listeners, two-phase init, synchronizer fallback, FDv1 fallback, and availability/offline behavior.Reviewed by Cursor Bugbot for commit 938eff1. Bugbot is set up for automated code reviews on this repo. Configure here.