Skip to content

rtc: fail fast instead of hanging when FFI is used across fork()#740

Open
theomonnom wants to merge 1 commit into
mainfrom
theo/ffi-fork-guard
Open

rtc: fail fast instead of hanging when FFI is used across fork()#740
theomonnom wants to merge 1 commit into
mainfrom
theo/ffi-fork-guard

Conversation

@theomonnom

Copy link
Copy Markdown
Member

The native runtime (tokio worker/IO threads in liblivekit_ffi) does not survive fork(), and re-initializing only resets the event callback rather than rebuilding the runtime. A child process that inherited an already-initialized FfiClient singleton therefore hangs on every FFI request.

Guard FfiClient.instance with the owning pid and raise a clear RuntimeError instead of hanging. This does not affect spawn/forkserver (each process initializes its own FFI); it only turns the fork-after-init footgun into an actionable error.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@davidzhao davidzhao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Comment thread livekit-rtc/livekit/rtc/_ffi_client.py Outdated
assert FfiClient.instance._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
ffi = FfiClient._owned_instance()
if ffi is not None:
assert ffi._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will assert be stripped out if our customers use some optimization flags ?

if that gets stripped out, I will assume ffi._ffi_lib.livekit_ffi_drop_handle will never be called.

@xianshijing-lk xianshijing-lk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm assuming you will address the comment about 'assert'

@theomonnom theomonnom force-pushed the theo/ffi-fork-guard branch from e192426 to d06cfdd Compare July 2, 2026 00:52
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@theomonnom theomonnom force-pushed the theo/ffi-fork-guard branch 2 times, most recently from 3a092f3 to 389b573 Compare July 2, 2026 01:26
@xianshijing-lk

Copy link
Copy Markdown
Contributor

oh, one question, is there any way to add a test for the fork behavior ? so that we can prevent regression

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.
@theomonnom theomonnom force-pushed the theo/ffi-fork-guard branch from 389b573 to 5fdf66e Compare July 6, 2026 06:50

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Callback still uses auto-creating singleton property, inconsistent with dispose()

The ffi_event_callback at livekit-rtc/livekit/rtc/_ffi_client.py:194 still accesses FfiClient.instance (the auto-creating @classproperty), while FfiHandle.dispose() was deliberately changed to use FfiClient._instance (the raw class variable) to avoid auto-creation during shutdown or in forked children. In practice this is likely safe because native threads aren't inherited across fork(), so the callback shouldn't fire in a child process. However, for consistency with the fork-safety intent of this PR, it might be worth using FfiClient._instance here too (with a None guard).

(Refers to line 194)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants