Skip to content

Reuse inactive publisher transceivers in RTCEngine to reduce memory growth on republish#1827

Open
moufmouf wants to merge 2 commits intolivekit:mainfrom
workadventure:reuse_transceiver
Open

Reuse inactive publisher transceivers in RTCEngine to reduce memory growth on republish#1827
moufmouf wants to merge 2 commits intolivekit:mainfrom
workadventure:reuse_transceiver

Conversation

@moufmouf
Copy link

Why?

I'm developing WorkAdventure, a virtual space platform and we use Livekit for big meetings (more than 4 participants). Under 4 participants, we use a direct P2P mesh. I'm telling this because we have a workflow where we manage the MediaTracks (they are created by us, not Livekit JS client SDK) and when we unpublish a track, we usually set stopOnUnpublish: false when calling localParticipant.unpublishTrack.

The issue that triggered this PR is that when we start publishing a screen sharing, the memory goes up by about 400MB. We stop publishing the screen sharing with localParticipant.unpublishTrack, the memory does not go down. We start publishing another screen sharing again (maybe another window), the memory goes up by another 400MB, etc...
In short, we have a memory leak, where each started screen sharing is never releasing memory.

As a side note, using track.pauseUpstream to stop the track then replace the track with another track later seems to work better, but this workflow causes other issues.

Finding the memory leak

I tried to track down where the memory leak was, and after a few trial and errors (and with the help of Codex), I ended up finding that the transceivers in the RTCEngine seem to be piling up each time we start a new track. The RTCRtpTransceiver seem to hold a reference to the MediaTrack that is never freed and eating a lot of memory. I did not find any way to properly free the MediaTrack reference, but Codex (again) proposed me the patch below. Basically, it tries to reuse an inactive existing sender that shares the same track "kind".

I tested this with WorkAdventure and the patch works. When I stop / start many screen sharing many times, the memory does not pile up. I also re-read the patch and validated it myself (to avoid any AI slop).

Note: I tried an alternative: freeing the memory as soon as localParticipant.unpublishTrack is called (using replaceTrack(null)) instead of waiting for the next track to start to reuse the transceiver. Alas, I could not make this work (that would be ideal as the memory would be freed as soon as we finish publishing... here, we have to wait getting out of the Livekit room for the memory to be freed)

What do you think of this patch? I can happily work on it if it needs any improvement.

…ansceiver churn

`RTCEngine.createSender`/`createSimulcastSender` always created new publisher transceivers via addTransceiver().
During repeated unpublish/publish cycles, this could accumulate inactive transceivers and increase memory usage.

Add a reuse path that:

- looks for an inactive publisher transceiver of matching media kind
- switches it back to sendonly
- replaces its sender track instead of creating a new transceiver
- applies to both primary sender creation and simulcast sender creation

This reduces transceiver churn and keeps memory growth bounded in restart-heavy flows (e.g. screenshare stop/start loops).
@CLAassistant
Copy link

CLAassistant commented Feb 27, 2026

CLA assistant check
All committers have signed the CLA.

@changeset-bot
Copy link

changeset-bot bot commented Feb 27, 2026

🦋 Changeset detected

Latest commit: d3b394d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
livekit-client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

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.

2 participants