Skip to content

feat: Add async event processor - #472

Open
jsonbailey wants to merge 7 commits into
mainfrom
jb/sdk-2769/async-event-processor
Open

feat: Add async event processor#472
jsonbailey wants to merge 7 commits into
mainfrom
jb/sdk-2769/async-event-processor

Conversation

@jsonbailey

@jsonbailey jsonbailey commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Adds the async analytics event delivery component for the async SDK client, extracted from the async SDK implementation branch (SDK-60).

Stacked on #471 (AsyncConfig) — review that first. Until #471 merges this PR shows its commits too; after merge a rebase drops them out.

What's here

  • AsyncEventProcessor interface + DefaultAsyncEventProcessor concrete implementation, mirroring the sync EventProcessor/DefaultEventProcessor split. send_event/flush are sync; stop is a coroutine.
  • Shared sans-I/O helpers extracted into event_processor_common.py so the sync and async processors share buffering, output formatting, and dispatch logic.
  • Tests mirroring the sync DefaultEventProcessor suite over an injected mock aiohttp session.

SDK-2769

feat: Add async event processor


Note

Medium Risk
Touches analytics delivery and refactors shared dispatch used by sync and async; risk is mitigated by extracting logic without intended behavior changes and broad async test coverage.

Overview
Adds DefaultAsyncEventProcessor and an async EventDispatcher so the async SDK can queue, batch, flush, and POST analytics and diagnostic events over AsyncHTTPTransport, with the same inbox messaging, timers, gzip, retries, and flush_and_wait / async stop behavior as the sync processor.

Shared non-I/O dispatch logic moves into EventDispatcherBase in event_processor_common.py (_process_event, context dedup, debug handling, _handle_response), and the sync EventDispatcher now subclasses it. CURRENT_EVENT_SCHEMA is centralized there for both paths.

Concurrent flush work on the async side uses BoundedTaskSet (replacing AsyncWorkerPool) with try_run for bounded concurrency and backpressure. A large pytest suite mirrors the sync DefaultEventProcessor tests against a mock aiohttp session.

Reviewed by Cursor Bugbot for commit 8c14ed7. Bugbot is set up for automated code reviews on this repo. Configure here.

@jsonbailey
jsonbailey force-pushed the jb/sdk-2769/async-event-processor branch 2 times, most recently from e7d9e77 to 0c83486 Compare July 29, 2026 18:38
Base automatically changed from jb/sdk-2768/async-config to main July 29, 2026 19:25
Implement the new AsyncEventProcessor interface with a concrete
DefaultAsyncEventProcessor, matching the sync DefaultEventProcessor
naming convention.
Triggers a flush and awaits delivery via a new inbox message, returning
whether it completed within the timeout.
@jsonbailey
jsonbailey force-pushed the jb/sdk-2769/async-event-processor branch from 0c83486 to ccb9c58 Compare July 29, 2026 19:31
The async event delivery concurrency limiter no longer mirrors the sync
FixedThreadPool's shape. BoundedTaskSet drops the unused name parameter and
the thread vocabulary, reserves a slot synchronously at spawn (so a full set
rejects rather than queues), and uses a done-callback plus asyncio.gather for
cleanup and draining.
@jsonbailey
jsonbailey force-pushed the jb/sdk-2769/async-event-processor branch from 2230024 to 5ed9484 Compare July 30, 2026 16:30
Remove the banner-style section headings; where they carried a useful
note, capture it as a short class docstring instead.
"""A fixed-size pool of concurrent tasks that rejects jobs when its limit
is reached. Matches the contract of
``ldclient.impl.fixed_thread_pool.FixedThreadPool``."""
class BoundedTaskSet:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The AsyncWorkerPool was previously created to mirror the FixedThreadPool but some of the naming and params didn't seem to align with the async code.

…ction comments

Rename drain() back to wait() (it awaits the in-flight tasks; it does not
halt intake), simplify the job param to Callable[[], Coroutine], and remove
banner-style section-heading comments from the async event/concurrency tests.
…cessors

Move the event schema version constant into event_processor_common so both
processors advertise the same X-LaunchDarkly-Event-Schema and can't drift.
@jsonbailey
jsonbailey marked this pull request as ready for review July 30, 2026 20:30
@jsonbailey
jsonbailey requested a review from a team as a code owner July 30, 2026 20:30

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 using default effort 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.

Reviewed by Cursor Bugbot for commit 8c14ed7. Configure here.

elif message.type == 'flush_and_wait':
self._trigger_flush()
await self._flush_workers.wait()
message.param.set()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

flush_and_wait false success on saturation

Medium Severity

flush_and_wait treats completion of currently in-flight workers as successful delivery. If _trigger_flush cannot start a send because BoundedTaskSet is at its limit, events stay in the outbox while the handler still signals the waiter, so callers get True even though those events were never handed off.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8c14ed7. Configure here.

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.

1 participant