-
Notifications
You must be signed in to change notification settings - Fork 23
perf: allow pubsub actor messages to be handled in parallel #659
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Concurrent handling via
FuturesUnorderedbreaks subscribe→unsubscribe ordering assumptions and can leak subscriptionsThe new
FuturesUnordered-based loop makeshandle_msgfutures for all message types run concurrently. Because of howhandle_msgis structured:AccountSubscribe:Self::add_sub(...).await.add_subinserts theAccountSubscriptionintosubsbefore its first.await(account_subscribe().await).AccountUnsubscribe:.await; its entire branch is synchronous and completes on the first poll of its future.With
FuturesUnordered(and without any explicit ordering guarantees), there is now a valid interleaving where:pubkeyis received first and itshandle_msgfuture is pushed intopending_messages.pubkeyis received and itshandle_msgfuture is also pushed.pending_messages.next()runs, it may poll the unsubscribe future before the subscribe future’s first poll.subs.lock().get(&pubkey)and finds no entry yet, so it returnsAccountSubscriptionDoesNotExist.add_subinserts theAccountSubscriptioninto the map and sets up the real subscription.This results in:
AccountSubscriptionDoesNotExist).add_subcompletes later and spawns the update task).add_subabout “messages to this actor are processed in the order they are sent” no longer being true under the concurrentFuturesUnorderedprocessing.This is a semantic correctness regression introduced by the concurrency change, not just a theoretical concern.
Consider one of the following fixes:
Restrict concurrency to subscription establishment only:
AccountUnsubscribe,Reconnect).AccountSubscribe, enqueue/track only the long‑running “establish subscription” work inFuturesUnordered(e.g., a dedicated async function that performsaccount_subscribe+ spawn of the update loop), while the mainhandle_msgpath that updatessubsand returns the oneshot response preserves message ordering.Explicitly track in‑flight subscriptions:
Pubkeystate for “subscription being created”.subs).Additionally, on shutdown or when
messages_receiveris closed, the loopbreaks and dropspending_messages, abruptly canceling in‑flighthandle_msgfutures. This is a behavior change from the previous sequential implementation, where the currently executinghandle_msgwould finish before exit. Please double‑check that it’s acceptable for callers to sometimes see pending subscribe/reconnect requests fail with dropped oneshots during shutdown.Also applies to: 232-252, 257-257, 362-370, 376-381
🌐 Web query:
💡 Result:
Short answer: No — there is no guarantee. FuturesUnordered is unordered: pushing a future does not poll it, and futures are driven only when they generate wake notifications, so a future pushed later can be polled (and even complete) before an earlier-pushed future’s first poll. Use FuturesOrdered if you need FIFO semantics. [1][2]
Sources:
The web search confirms the review comment's analysis is correct. FuturesUnordered provides no guarantees about polling order—a future pushed later can be polled and complete before an earlier-pushed future's first poll; FuturesOrdered is the correct choice for FIFO semantics. This directly validates the identified race condition where an unsubscribe could complete before a preceding subscribe's critical section (insertion into
subs) executes.The review comment is accurate and comprehensive. The semantic correctness regression is real and actionable.
Concurrent handling via
FuturesUnorderedbreaks subscribe→unsubscribe ordering assumptions and can leak subscriptionsThe new
FuturesUnordered-based loop makeshandle_msgfutures for all message types run concurrently. Because of howhandle_msgis structured:AccountSubscribe:Self::add_sub(...).await.add_subinserts theAccountSubscriptionintosubsbefore its first.await(account_subscribe().await).AccountUnsubscribe:.await; its entire branch is synchronous and completes on the first poll of its future.With
FuturesUnordered(which provides no ordering guarantees), there is now a valid interleaving where:pubkeyis received first and itshandle_msgfuture is pushed intopending_messages.pubkeyis received and itshandle_msgfuture is also pushed.pending_messages.next()runs, it may poll the unsubscribe future before the subscribe future's first poll.subs.lock().get(&pubkey)and finds no entry yet, so it returnsAccountSubscriptionDoesNotExist.add_subinserts theAccountSubscriptioninto the map and sets up the real subscription.This results in:
AccountSubscriptionDoesNotExist).add_subcompletes later and spawns the update task).add_subabout "messages to this actor are processed in the order they are sent" no longer being true under the concurrentFuturesUnorderedprocessing.Consider one of the following fixes:
Restrict concurrency to subscription establishment only:
AccountUnsubscribe,Reconnect).AccountSubscribe, enqueue/track only the long‑running "establish subscription" work inFuturesUnordered(e.g., a dedicated async function that performsaccount_subscribe+ spawn of the update loop), while the mainhandle_msgpath that updatessubsand returns the oneshot response preserves message ordering.Explicitly track in‑flight subscriptions:
Pubkeystate for "subscription being created".subs).Additionally, on shutdown or when
messages_receiveris closed, the loopbreaks and dropspending_messages, abruptly canceling in‑flighthandle_msgfutures. This is a behavior change from the previous sequential implementation, where the currently executinghandle_msgwould finish before exit. Please double‑check that it's acceptable for callers to sometimes see pending subscribe/reconnect requests fail with dropped oneshots during shutdown.Also applies to: lines 232-252, 257, 362-370, 376-381