Skip to content

Commit 5472a7c

Browse files
committed
rtc: route all FFI cleanup paths through a fork-safe helper
The fork guard raised from FfiClient.instance, which the *Stream/Room __del__ methods and FfiHandle.dispose all call - so a fork child printed 'Exception ignored in __del__' to stderr for each inherited object. Add FfiClient._owned_instance() (returns None when not the owning pid) and use it in every cleanup/GC path so children no-op quietly instead of touching (or dropping handles on) the inherited native state.
1 parent eee260c commit 5472a7c

4 files changed

Lines changed: 21 additions & 11 deletions

File tree

livekit-rtc/livekit/rtc/_ffi_client.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,9 @@ def disposed(self) -> bool:
8383
def dispose(self) -> None:
8484
if self.handle != INVALID_HANDLE and not self._disposed:
8585
self._disposed = True
86-
try:
87-
ffi = FfiClient.instance
88-
except RuntimeError:
89-
# Inherited across fork(): the handle belongs to the parent's
90-
# FFI and is reclaimed when this process exits. Skip quietly so
91-
# __del__ in the child doesn't spam "Exception ignored" to stderr.
92-
return
93-
assert ffi._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
86+
ffi = FfiClient._owned_instance()
87+
if ffi is not None:
88+
assert ffi._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
9489

9590
def __repr__(self) -> str:
9691
return f"FfiHandle({self.handle})"
@@ -235,6 +230,15 @@ def instance(cls) -> "FfiClient":
235230
cls._instance = inst = FfiClient()
236231
return inst
237232

233+
@classmethod
234+
def _owned_instance(cls) -> Optional["FfiClient"]:
235+
# Cleanup/GC paths (FfiHandle.dispose, *Stream.__del__, Room.__del__)
236+
# use this instead of `instance` so they no-op in a fork child rather
237+
# than raising the fork guard, which __del__ would surface on stderr as
238+
# "Exception ignored in...". A child must not touch the inherited FFI.
239+
inst = cls._instance
240+
return inst if inst is not None and inst._pid == os.getpid() else None
241+
238242
def __init__(self) -> None:
239243
self._pid = os.getpid()
240244
self._lock = threading.RLock()

livekit-rtc/livekit/rtc/audio_stream.py

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

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

251253
def _create_owned_stream(self) -> Any:
252254
assert self._track is not None

livekit-rtc/livekit/rtc/room.py

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

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

195197
@property
196198
async def sid(self) -> str:

livekit-rtc/livekit/rtc/video_stream.py

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

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

114116
def _create_owned_stream(self) -> Any:
115117
assert self._track is not None

0 commit comments

Comments
 (0)