Skip to content

Commit fd9ef51

Browse files
committed
rtc: put fork guard in request() instead of instance
Guarding FfiClient.instance meant every __del__/dispose path that reads it raised the guard in fork children (noisy 'Exception ignored'), which needed extra cleanup plumbing. request() is the only place that actually hangs on a dead post-fork runtime, so guard there: active use raises loudly, while instance and the GC paths revert to their original behavior. atexit dispose stays pid-guarded (it needs the runtime and would hang at exit).
1 parent f5270bd commit fd9ef51

4 files changed

Lines changed: 17 additions & 34 deletions

File tree

livekit-rtc/livekit/rtc/_ffi_client.py

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,8 @@ def disposed(self) -> bool:
8383
def dispose(self) -> None:
8484
if self.handle != INVALID_HANDLE and not self._disposed:
8585
self._disposed = True
86-
ffi = FfiClient._owned_instance()
87-
if ffi is not None:
88-
dropped = ffi._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
89-
assert dropped
86+
dropped = FfiClient.instance._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
87+
assert dropped
9088

9189
def __repr__(self) -> str:
9290
return f"FfiHandle({self.handle})"
@@ -216,27 +214,9 @@ class FfiClient:
216214

217215
@classproperty
218216
def instance(cls) -> "FfiClient":
219-
inst = cls._instance
220-
if inst is not None and inst._pid != os.getpid():
221-
# The native runtime (tokio worker/IO threads inside liblivekit_ffi)
222-
# does not survive fork(), and re-initializing does not rebuild it,
223-
# so a child that inherited an initialized FFI hangs on every request.
224-
# Fail loudly instead of hanging.
225-
raise RuntimeError(
226-
"livekit.rtc was used in a parent process before fork(); the native "
227-
"runtime cannot be used across fork(). Do not create or connect a Room "
228-
"(or use any livekit.rtc object) before forking child processes."
229-
)
230-
if inst is None:
231-
cls._instance = inst = FfiClient()
232-
return inst
233-
234-
@classmethod
235-
def _owned_instance(cls) -> Optional["FfiClient"]:
236-
# the client only if this process created it; cleanup paths use it to
237-
# no-op in fork children instead of touching inherited FFI/locks
238-
inst = cls._instance
239-
return inst if inst is not None and inst._pid == os.getpid() else None
217+
if cls._instance is None:
218+
cls._instance = FfiClient()
219+
return cls._instance
240220

241221
def __init__(self) -> None:
242222
self._pid = os.getpid()
@@ -291,6 +271,15 @@ def queue(self) -> FfiQueue[proto_ffi.FfiEvent]:
291271
return self._queue
292272

293273
def request(self, req: proto_ffi.FfiRequest) -> proto_ffi.FfiResponse:
274+
if self._pid != os.getpid():
275+
# The native runtime does not survive fork() and cannot be
276+
# reinitialized, so requests from a child that inherited the FFI
277+
# would hang. Fail loudly instead.
278+
raise RuntimeError(
279+
"livekit.rtc was used in a parent process before fork(); the native "
280+
"runtime cannot be used across fork(). Do not create or connect a Room "
281+
"(or use any livekit.rtc object) before forking child processes."
282+
)
294283
proto_data = req.SerializeToString()
295284
proto_len = len(proto_data)
296285
data = (ctypes.c_ubyte * proto_len)(*proto_data)

livekit-rtc/livekit/rtc/audio_stream.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,7 @@ def from_track(
246246
)
247247

248248
def __del__(self) -> None:
249-
ffi = FfiClient._owned_instance()
250-
if ffi is not None:
251-
ffi.queue.unsubscribe(self._ffi_queue)
249+
FfiClient.instance.queue.unsubscribe(self._ffi_queue)
252250

253251
def _create_owned_stream(self) -> Any:
254252
assert self._track is not None

livekit-rtc/livekit/rtc/room.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,7 @@ def __init__(
190190

191191
def __del__(self) -> None:
192192
if self._ffi_handle is not None:
193-
ffi = FfiClient._owned_instance()
194-
if ffi is not None:
195-
ffi.queue.unsubscribe(self._ffi_queue)
193+
FfiClient.instance.queue.unsubscribe(self._ffi_queue)
196194

197195
@property
198196
async def sid(self) -> str:

livekit-rtc/livekit/rtc/video_stream.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,7 @@ def from_track(
109109
)
110110

111111
def __del__(self) -> None:
112-
ffi = FfiClient._owned_instance()
113-
if ffi is not None:
114-
ffi.queue.unsubscribe(self._ffi_queue)
112+
FfiClient.instance.queue.unsubscribe(self._ffi_queue)
115113

116114
def _create_owned_stream(self) -> Any:
117115
assert self._track is not None

0 commit comments

Comments
 (0)