feat: Add async FDv1 polling data source and feature requester - #475
Conversation
b8b7f52 to
16c4438
Compare
fff0f5e to
5053397
Compare
ca9d5b1 to
ef5803c
Compare
|
Note This is a comment from Claude, an AI tool. @rlamb ran a multi-agent review of this PR and asked Claude to post this finding with a test. Problem:
|
Drop the async feature requester's duplicate endpoint definition; use the shared constant from datasource_common instead.
- Don't set _ready on a generic poll exception, so a transient error during startup no longer ends start_wait early (matches sync). - Close the owned HTTP transport on stop: the feature requester tracks whether it created the transport and exposes close(); the polling processor awaits it. - Drop the dead 'all_data is not None' guard (the requester returns cached data on 304, never None) and the fictional None-return polling test.
AsyncRepeatingTask gains wait_stopped() to await the cancelled task; the polling processor's stop() now waits for the in-flight poll to unwind before closing the requester's transport, so awaiting stop() guarantees background work has stopped and the transport isn't closed under a live request.
Addresses review findings on the async FDv1 polling data source: - Drop the store.initialized gate on the VALID status update so async polling reports VALID on every successful poll like the sync data source, instead of getting stuck in INITIALIZING when a store's initialized flag is a false/cached read. - Add an AsyncFeatureRequester interface and implement it, replacing the subclass of the stale sync FeatureRequester ABC (whose get_all method the impl never provided). Keeps get_all_data, which matches the sync implementation, and types the processor's requester param against the interface. - Minor: use plain truthiness in initialized(), spec the test sink as AsyncDataSourceUpdateSink, and simplify the make_config docstring.
AsyncPollingUpdateProcessor.stop awaits requester.close(), and the requester param is typed against AsyncFeatureRequester, so the interface must declare close() — otherwise a substitute requester implementing only the interface would fail at shutdown.
…pped
wait_stopped awaited the worker with a bare await + except CancelledError, which conflated the worker's expected stop() cancellation with cancellation of the caller itself — a timed/cancelled stop() could swallow the cancel and return as if it completed. Use asyncio.wait({task}) (as join_handle already does): it absorbs the worker's cancellation without re-raising it, while still propagating a cancellation of the caller.
4ca4f5d to
357fbcf
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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 357fbcf. Configure here.
…not leak it AsyncPollingUpdateProcessor.stop() awaited wait_stopped() and only then closed the requester. If the caller of stop() was cancelled during that wait, close() never ran and an owned aiohttp transport could leak. Move the close into a finally so it still runs on cancellation, while letting CancelledError propagate. Adds a regression test that cancels stop() mid-wait and asserts the transport is still closed.

Overview
PR 7 of the SDK-60 async epic: the async FDv1 polling data source and feature requester.
async_polling.py— async FDv1 polling update processor. Polls the feature requester on an interval and pushes flag/segment data into the data source update sink, updating data source status (VALID / OFF) as appropriate.async_feature_requester.py— async FDv1 feature requester that fetches the full flag/segment payload over HTTP.test_async_polling.py— unit tests for the async polling update processor.Stacking
This PR is stacked on #464 (base branch
jb/sdk-2743/async-fdv1-streaming), which provides the shareddatasource_commonmodule these files import. Until #464 merges, this PR will also show #464's commits in its diff; a rebase after #464 merges will drop them, leaving only the three files here.SDK-2825
Note
Medium Risk
New experimental async flag-ingestion path affects client initialization and data-source status; shutdown and HTTP error handling must stay correct to avoid leaks or stuck waits.
Overview
Adds experimental async FDv1 polling: an
AsyncFeatureRequestercontract plusAsyncFeatureRequesterImplthat GETs the poll endpoint with gzip, optional payload filter query param, and ETag / 304 caching before returning flags and segments.AsyncPollingUpdateProcessorruns polls onAsyncRepeatingTask, writes viasink_or_store, sets ready when the store initializes, and reports VALID / INTERRUPTED / OFF on success, recoverable errors, and fatal HTTP failures (matching sync polling semantics, including unblocking init on unrecoverable errors).AsyncRepeatingTask.wait_stopped()lets shutdown wait for the in-flight poll to finish;stop()on the processor uses that (withrequester.close()infinally) so transports are not closed mid-request.Broad unit tests cover caching, transport ownership, error recovery, sink status, and shutdown ordering.
Reviewed by Cursor Bugbot for commit 305f2b0. Bugbot is set up for automated code reviews on this repo. Configure here.