Skip to content

fix(rtc): avoid AssertionError in FfiHandle.__del__ after global FFI disposal - #857

Open
NabiBukhsh-AI wants to merge 2 commits into
livekit:mainfrom
NabiBukhsh-AI:fix/845-ffi-handle-shutdown-assert
Open

NabiBukhsh-AI wants to merge 2 commits into
livekit:mainfrom
NabiBukhsh-AI:fix/845-ffi-handle-shutdown-assert

Conversation

@NabiBukhsh-AI

Copy link
Copy Markdown

Fixes #845

Problem

When a process exits (e.g. at job shutdown), the global livekit_ffi_dispose function is called via an atexit hook (_dispose_lk_ffi). This tears down the native runtime and releases all native handles globally.

During subsequent interpreter finalization / cyclic garbage collection (Py_FinalizeEx), remaining Python objects holding FfiHandles (such as Room or LocalParticipant) have their destructors (__del__) called. __del__ calls FfiHandle.dispose(), which executes livekit_ffi_drop_handle. Because the native runtime has already been disposed, livekit_ffi_drop_handle returns false, causing:

Exception ignored in: <function FfiHandle.__del__ at 0x...>
Traceback (most recent call last):
  File ".../site-packages/livekit/rtc/_ffi_client.py", line 77, in __del__
    self.dispose()
  File ".../site-packages/livekit/rtc/_ffi_client.py", line 99, in dispose
    assert dropped
AssertionError:

Solution

  1. Weakly track live FfiHandle instances via _live_handles = weakref.WeakSet().
  2. When _dispose / livekit_ffi_dispose executes, record FfiClient._ffi_disposed = True and mark all surviving handles consumed (handle.mark_consumed(), setting _disposed = True).
  3. In FfiHandle.dispose(), check if the FFI runtime has already been disposed before invoking livekit_ffi_drop_handle.
  4. During active runtime, invalid or duplicate handle drops continue to assert as expected.

Verification

  • Added regression tests in livekit-rtc/tests/test_ffi_handle_lifecycle.py:
    • Verified active runtime invalid handle drops continue to raise AssertionError.
    • Verified handles are marked consumed upon FfiClient.instance._dispose() and subsequent GC/dispose calls do not assert.
    • Verified a child Python process allocating a handle and exiting with the handle alive terminates with status 0 without any AssertionError or ignored destructor exceptions on stderr.
  • All checks and existing test suites pass cleanly.

livekit#845)

When the process exits, livekit_ffi_dispose clears all native handles globally. Subsequent interpreter finalization / garbage collection then invokes __del__ on surviving FfiHandle wrappers, which calls livekit_ffi_drop_handle. Since the native runtime is already disposed, the call returns false, causing an AssertionError in FfiHandle.dispose.

This weakly tracks live FfiHandle instances and marks them consumed when _dispose / livekit_ffi_dispose runs. Additionally, dispose checks if the FFI runtime has already been disposed before invoking livekit_ffi_drop_handle.

Fixes livekit#845
@CLAassistant

CLAassistant commented Oct 11, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@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: 1 flag

Not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

This branch has not been deployed

No deployments
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.

AssertionError in FfiHandle.__del__ (assert dropped) at job shutdown with livekit 1.1.20

2 participants