From 5ad0eb728806266f32f5c781e4cd5784e21bc1b7 Mon Sep 17 00:00:00 2001 From: Long Chen Date: Tue, 3 Nov 2026 14:27:46 +0800 Subject: [PATCH] fix(rtc): create the FfiClient singleton under a lock Two threads that read FfiClient.instance at the same time each created their own client, and FFI events reached only one of their queues. A Room.connect() listening on the other queue never sent ReadyForRoomEventRequest, so the FFI server panicked after 15 s and the process exited with every room in it. Fixes livekit/agents#7545. --- .changeset/ffi-client-singleton-lock.md | 5 +++++ livekit-rtc/livekit/rtc/_ffi_client.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .changeset/ffi-client-singleton-lock.md diff --git a/.changeset/ffi-client-singleton-lock.md b/.changeset/ffi-client-singleton-lock.md new file mode 100644 index 00000000..2aabdd8c --- /dev/null +++ b/.changeset/ffi-client-singleton-lock.md @@ -0,0 +1,5 @@ +--- +"livekit-rtc": patch +--- + +Create the shared `FfiClient` under a lock, so rooms connected from several threads at once no longer lose their connect callback and panic the process. diff --git a/livekit-rtc/livekit/rtc/_ffi_client.py b/livekit-rtc/livekit/rtc/_ffi_client.py index 096b7ea5..d45c3247 100644 --- a/livekit-rtc/livekit/rtc/_ffi_client.py +++ b/livekit-rtc/livekit/rtc/_ffi_client.py @@ -231,12 +231,17 @@ def to_python_level(level: proto_ffi.LogLevel.ValueType) -> Optional[int]: class FfiClient: _instance: Optional["FfiClient"] = None _ffi_disposed: bool = False + _instance_lock = threading.Lock() @classproperty def instance(cls) -> "FfiClient": - if cls._instance is None: - cls._instance = FfiClient() - return cls._instance + if cls._instance is not None: + return cls._instance + # threads that race here would each get their own client and lose events sent to the other + with cls._instance_lock: + if cls._instance is None: + cls._instance = FfiClient() + return cls._instance def __init__(self) -> None: FfiClient._ffi_disposed = False