Skip to content

fix(rtc_engine): hold a weak reference to the engine from its own task - #1434

Open
LautaroPetaccio wants to merge 2 commits into
livekit:mainfrom
LautaroPetaccio:fix/engine-task-weak-self-reference
Open

LautaroPetaccio wants to merge 2 commits into
livekit:mainfrom
LautaroPetaccio:fix/engine-task-weak-self-reference

Conversation

@LautaroPetaccio

@LautaroPetaccio LautaroPetaccio commented Sep 18, 2026

Copy link
Copy Markdown

The problem

engine_task took an Arc<EngineInner>:

tokio::spawn(Self::engine_task(inner.clone(), session_events, close_rx));

But the task's JoinHandle and the oneshot::Sender that stops it both live in EngineHandle (rtc_engine/mod.rs:266), which lives in running_handle inside that same EngineInner. The task therefore kept alive the only thing that could stop it — its own stop signal — and session_events' emitter is likewise reachable only through the session it pins.

An engine dropped without an explicit close() released nothing: the session, both peer connections, the Arc<LkRuntime> (and so the PeerConnectionFactory and its WebRTC threads), the signal client with its open websocket, and the task itself all stayed resident for the lifetime of the process. The SFU also kept its half of the session, since the socket was never closed.

There is no Drop on RtcEngine, EngineInner, RtcSession or SessionInner, so nothing else could recover it.

The fix

RtcSession already avoids exactly this by keeping close_tx in SessionHandle, outside the Arc'd inner, so dropping it stops its tasks. Rather than restructure EngineHandle, the task now takes a Weak and upgrades per event.

This is what AGENTS.md already requires:

If an owner transitively stores the callback/task, the callback/task must not strongly capture that owner.

A side benefit is that the task now stops on its own once the engine is gone, instead of depending solely on the close signal.

Verification

test_drop_without_close_releases_engine connects an engine, takes a drop probe, drops it without close(), and waits for the internals to be released.

  • Before: fails — still alive after the full 10s window.
  • After: passes, in 0.03s.

Full e2e suite against livekit-server --dev is green, including all six reconnection tests. The only delta from the baseline is the added test.

The test targets RtcEngine directly rather than going through Room, because the room layer has the same structural issue with its own close_tx and would mask an engine-level fix. test_engine() is added to the e2e harness for that purpose.

`engine_task` took an `Arc<EngineInner>`, but the task's `JoinHandle` and the
oneshot sender that stops it both live in `EngineHandle`, inside that same
`EngineInner`. The task therefore kept alive the only thing that could ever
stop it. An engine dropped without an explicit `close()` released nothing: the
session, both peer connections, the signal client with its open websocket, and
the task itself stayed resident for the lifetime of the process, and the SFU
kept its half of the session because the socket was never closed.

`RtcSession` already avoids this by keeping `close_tx` in `SessionHandle`,
outside the `Arc`'d inner. Rather than restructure the handle here, take a
`Weak` and upgrade per event, which is what AGENTS.md prescribes for a task
whose owner stores it. The task now also stops on its own once the engine is
gone, instead of depending solely on the close signal.

Adds an e2e test asserting the internals are released after a drop without
`close()`, and a `test_engine()` harness helper so engine lifecycle can be
observed without the room's own ownership edges masking it.

@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

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.

1 participant