Summary
PR #564 added filter_fn to FfiQueue.subscribe() and applied it to AudioStream / VideoStream. Room.connect() still does an unfiltered long-lived subscribe:
self._ffi_queue = FfiClient.instance.queue.subscribe(self._loop)
Room._listen_task only consumes room_event and rpc_method_invocation, then always does put_nowait + await self._room_queue.join(). Every audio_stream_event is therefore delivered to every connected Room and costs a full event-loop round-trip each time.
Production impact
We run many 1:1 voice rooms in one Python process (device WebSocket gateway → rtc.Room per session). With default 10 ms audio frames:
- 8 rooms: ~7.5k listen-task wakeups/s — barely holds
- 16 rooms: ~30k wakeups/s — event loop saturates
Symptoms match #564 and #390: room.connect() futures convoy-resolve after 10–60 s, WebSocket keepalive pings time out, CPU is idle (GIL / single loop), not compute-bound.
AudioSource.capture_frame() is a short-lived subscribe waiting on capture_audio_frame (the request callback), so it should not be filtered to audio_source_event.
Suggested fix
In Room.connect(), subscribe with a filter matching what _listen_task actually reads:
self._ffi_queue = FfiClient.instance.queue.subscribe(
self._loop,
filter_fn=lambda e: e.WhichOneof(message) in (room_event, rpc_method_invocation),
)
This is the same pattern as #564, just completing it for Room. Happy to send a PR if that helps.
Environment
- livekit==1.1.8 / livekit-api==1.1.0
- one process, many
rtc.Room instances
- no
frame_size_ms on AudioStream (defaults to 10 ms)
Summary
PR #564 added
filter_fntoFfiQueue.subscribe()and applied it toAudioStream/VideoStream.Room.connect()still does an unfiltered long-lived subscribe:Room._listen_taskonly consumesroom_eventandrpc_method_invocation, then always doesput_nowait+await self._room_queue.join(). Everyaudio_stream_eventis therefore delivered to every connectedRoomand costs a full event-loop round-trip each time.Production impact
We run many 1:1 voice rooms in one Python process (device WebSocket gateway →
rtc.Roomper session). With default 10 ms audio frames:Symptoms match #564 and #390:
room.connect()futures convoy-resolve after 10–60 s, WebSocket keepalive pings time out, CPU is idle (GIL / single loop), not compute-bound.AudioSource.capture_frame()is a short-lived subscribe waiting oncapture_audio_frame(the request callback), so it should not be filtered toaudio_source_event.Suggested fix
In
Room.connect(), subscribe with a filter matching what_listen_taskactually reads:This is the same pattern as #564, just completing it for
Room. Happy to send a PR if that helps.Environment
rtc.Roominstancesframe_size_msonAudioStream(defaults to 10 ms)