Summary
A short Python script that uses the FFI and then returns normally sometimes aborts during interpreter shutdown, with exit code 134. This happens in about 0.5-1% of runs. It started as a flaky Docker build step for us.
thread 'tokio-rt-worker' (N) panicked at /rustc/8bab26f4f68e0e26f0bb7960be334d5b520ea452/library/core/src/panicking.rs:225:5:
panic in a function that cannot unwind
thread caused non-unwinding panic. aborting.
Aborted (core dumped)
Versions
livekit 1.1.18 (bundled livekit-ffi 0.12.76), pinned by livekit-agents 1.8.1–1.8.3
- It reproduces the same way with
livekit 1.1.20 installed over it.
- Python 3.12.13 (
python:3.12.13-slim-bookworm), Linux x86_64 (CI) and aarch64 (local Docker)
Reproduction
import asyncio
from livekit.agents.voice.background_audio import BackgroundAudioPlayer
async def main():
player = BackgroundAudioPlayer() # creates an rtc.AudioSource
await player.aclose()
asyncio.run(main())
Run it in a loop, for example for i in $(seq 1 400); do python repro.py || echo fail; done. We saw about 2 aborts in 200 runs on 1.1.18, and 2 in 400 with 1.1.20.
What we observed
- Every abort happens after
asyncio.run(main()) has returned. A marker printed right after it is always present in the output.
- Under gdb, the main thread is in
Py_FinalizeEx → PyGC_Collect, which runs after the atexit hook that calls livekit_ffi_dispose(). The aborting thread is a tokio-rt-worker that gdb reports as "(Exiting)". It panics inside liblivekit_ffi.so during thread exit, where the panic cannot unwind, so the process aborts.
- It looks like a runtime worker thread (possibly one spawned during teardown) outlives or races
livekit_ffi_dispose(), and panics in a thread-local destructor or thread-exit path.
- Ending the script with
os._exit(0), which skips finalization, gave 0 aborts in 1,000 runs.
Expected
A process that has finished using the SDK exits with status 0. Or, if the runtime cannot shut down cleanly, it does not abort.
Impact for us
- Build-time smoke checks that construct SDK audio objects fail about 1% of the time.
- Agent job processes are not affected, because they end with
os._exit from the multiprocessing forkserver.
Summary
A short Python script that uses the FFI and then returns normally sometimes aborts during interpreter shutdown, with exit code 134. This happens in about 0.5-1% of runs. It started as a flaky Docker build step for us.
Versions
livekit1.1.18 (bundledlivekit-ffi0.12.76), pinned bylivekit-agents1.8.1–1.8.3livekit1.1.20 installed over it.python:3.12.13-slim-bookworm), Linux x86_64 (CI) and aarch64 (local Docker)Reproduction
Run it in a loop, for example
for i in $(seq 1 400); do python repro.py || echo fail; done. We saw about 2 aborts in 200 runs on 1.1.18, and 2 in 400 with 1.1.20.What we observed
asyncio.run(main())has returned. A marker printed right after it is always present in the output.Py_FinalizeEx → PyGC_Collect, which runs after theatexithook that callslivekit_ffi_dispose(). The aborting thread is atokio-rt-workerthat gdb reports as "(Exiting)". It panics insideliblivekit_ffi.soduring thread exit, where the panic cannot unwind, so the process aborts.livekit_ffi_dispose(), and panics in a thread-local destructor or thread-exit path.os._exit(0), which skips finalization, gave 0 aborts in 1,000 runs.Expected
A process that has finished using the SDK exits with status 0. Or, if the runtime cannot shut down cleanly, it does not abort.
Impact for us
os._exitfrom the multiprocessing forkserver.