Skip to content

rtc: dispatch event handlers in registration order - #826

Closed
ubmids wants to merge 1 commit into
livekit:mainfrom
ubmids:fix/event-emitter-order
Closed

ubmids wants to merge 1 commit into
livekit:mainfrom
ubmids:fix/event-emitter-order

Conversation

@ubmids

@ubmids ubmids commented Sep 19, 2026

Copy link
Copy Markdown

Fixes #778.

EventEmitter stored handlers in a set, so emit walked them in hash order. That order is stable for the life of a process and arbitrary between runs, which is an awkward shape: a handler that reads a field a peer handler writes during the same emit is consistently right on one machine and consistently wrong on another, rather than flaky on both.

The reported case is in livekit-agents, where AgentActivity._on_metrics_collected stamps speech_id onto the emitted object before re-emitting it, so a second subscriber to the same plugin's metrics_collected sees the stamped value or None depending on which handler the set happens to yield first. #778 measured the pre-stamp state in 7 of 12 fresh processes.

_events is now a dict keyed by the handler, which is what #778 suggested. Insertion order is preserved and setdefault keeps the idempotent add a set gave, so registering the same handler twice still dispatches once and does not move it to the back.

Worth flagging up front:

On the tests: they pin handler hashes rather than registering a few callbacks and hoping a set reorders them. With three handlers that would pass on the broken implementation about one run in six, and a test that only usually fails is not much of a regression test. Four of the six fail on main; the other two cover the dedup and copy-during-emit behaviour, which were already correct and are there to stop the change quietly breaking them.

Checked with pytest livekit-rtc/tests (63 passed, 14 skipped), ruff check, ruff format --check and mypy.

Written with AI assistance; I have gone through every line and can speak to it.

EventEmitter kept handlers in a set, so emit walked them in hash order. That is
stable for the life of a process and arbitrary between runs, so a handler that
reads a field a peer handler writes during the same emit is correct on one
machine and wrong on another, consistently rather than intermittently.

livekit-agents hits this: AgentActivity._on_metrics_collected stamps speech_id
onto the emitted object before re-emitting, so a second subscriber to the same
plugin event sees the value or None depending on which handler the set yields
first. The reporter measured the pre-stamp state in 7 of 12 fresh processes.

A dict keyed by the handler keeps insertion order and the idempotent add a set
gave, so registering twice still dispatches once and does not reorder.

The ordering tests pin hashes rather than registering a few handlers and hoping
a set shuffles them, which would only fail about one run in six.

@devin-ai-integration devin-ai-integration Bot left a comment

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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@davidzhao

Copy link
Copy Markdown
Member

fixed by #802

@davidzhao davidzhao closed this Sep 19, 2026
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.

EventEmitter dispatches handlers in Set order — peer handlers observe pre-mutation event state non-deterministically

2 participants