Skip to content

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

Description

@404n0signal

Summary

livekit.rtc.EventEmitter stores handlers in a set and emit() iterates a copy of it, so dispatch order is hash-derived rather than registration-ordered. Any handler that reads a field a peer handler mutates during the same emit gets a non-deterministic result — decided once per process, since handler identities are stable.

Concrete case (livekit-agents 1.6.4)

AgentActivity._on_metrics_collected stamps speech_id onto LLM/TTS metrics by mutating the emitted object (ev.speech_id = speech_handle.id) before re-emitting on the session. A user handler subscribed to the same plugin's metrics_collected therefore sees speech_id either stamped or None, depending on which handler the set yields first.

Because the order is fixed per process, this manifests as an all-or-nothing pattern: entire sessions where 100% of plugin-level metrics are unkeyed, and other sessions at 100% keyed. Across a larger dataset it averaged out to roughly a coin flip.

Reproduction

Minimal repro: subscribe a second handler to an LLM plugin's metrics_collected alongside a running AgentSession, record metrics.speech_id as observed at dispatch time, repeat over fresh processes. In 12 fresh sessions our handler observed the pre-stamp (None) state in 7.

Suggestion

An insertion-ordered container for _events (e.g. dict keyed by handler, which preserves insertion order and keeps O(1) add/remove/dedup) would make dispatch deterministic and match the EventEmitter contract familiar from other ecosystems — and would let internal stamping handlers reliably run before user handlers registered later. Happy to open a PR if the direction is acceptable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions