Skip to content

Commit 3a092f3

Browse files
committed
rtc: fail fast instead of hanging when FFI is used across fork()
The native runtime (tokio worker/IO threads in liblivekit_ffi) does not survive fork() and cannot be rebuilt by re-initializing, so a child that inherited an initialized FfiClient would hang on every request. Guard request() with the owning pid and raise a clear error instead. The atexit dispose handler is likewise skipped in fork children (it needs the runtime and would hang at exit). Also move the drop_handle side effect out of an assert so it is not stripped under python -O.
1 parent 3f92f3c commit 3a092f3

1 file changed

Lines changed: 18 additions & 1 deletion

File tree

livekit-rtc/livekit/rtc/_ffi_client.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +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-
assert FfiClient.instance._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
86+
dropped = FfiClient.instance._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
87+
assert dropped
8788

8889
def __repr__(self) -> str:
8990
return f"FfiHandle({self.handle})"
@@ -218,6 +219,7 @@ def instance(cls) -> "FfiClient":
218219
return cls._instance
219220

220221
def __init__(self) -> None:
222+
self._pid = os.getpid()
221223
self._lock = threading.RLock()
222224
self._queue = FfiQueue[proto_ffi.FfiEvent]()
223225

@@ -253,16 +255,31 @@ def __init__(self) -> None:
253255
)
254256

255257
ffi_lib = self._ffi_lib
258+
init_pid = self._pid
256259

257260
@atexit.register
258261
def _dispose_lk_ffi() -> None:
262+
# A fork child inherits this handler but not the native runtime
263+
# threads; disposing the inherited (dead) FFI would hang or abort at
264+
# exit. Only the process that initialized it should dispose.
265+
if os.getpid() != init_pid:
266+
return
259267
ffi_lib.livekit_ffi_dispose()
260268

261269
@property
262270
def queue(self) -> FfiQueue[proto_ffi.FfiEvent]:
263271
return self._queue
264272

265273
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+
)
266283
proto_data = req.SerializeToString()
267284
proto_len = len(proto_data)
268285
data = (ctypes.c_ubyte * proto_len)(*proto_data)

0 commit comments

Comments
 (0)