client-sdk-swift icon indicating copy to clipboard operation
client-sdk-swift copied to clipboard

Memory Leak in LiveKitWebRTC when streaming via BufferCapturer

Open Mt-Perazim opened this issue 2 weeks ago • 2 comments

Hi @pblazej and @hiroshihorie , this is a follow-up to my previous report:
Previous issue: Memory Leaking - High memory after Room.disconnect #827

I can now provide a minimal, runnable LiveKit + ARKit reproduction project that isolates the leak.
This project contains only the essential components from the LiveKit example plus a minimal custom AR → BufferCapturer bridge.
This issue focuses exclusively on internal leaks.


Summary

The minimal project supports two Room lifecycle modes:

  • Global shared Room instance (matching the original LiveKit Example)
    → Reproduces one leak(LiveKitWebRTC)

  • Per-connection mode (fresh Room() for each join)
    → After a leave + rejoin, Instruments reports more than 24 leaks

In the per-connection mode, the previous Room instance becomes unreachable from the app side (replaced by a new placeholder Room), but internal LiveKit objects remain alive and are reported as leaks.


Minimal Reproduction Project

@pblazej and @hiroshihorie, I invited you to the private GitHub project.

The project is a very small SwiftUI app based on these components from the official LiveKit examples:

  • RoomSwitchView
  • RoomContextView
  • ParticipantView
  • ConnectView
  • LKButton, LKTextField
  • SecureStore
  • Participant+Helper
  • ExampleRoomMessage
  • ConnectionHistory
  • Bundle helper

Added on top:

  • A minimal ARKit → BufferCapturer bridge (ARVideoCapturer) publishing AR frames as a LocalVideoTrack.

How to Run the Minimal Setup

  1. Clone the provided sample project.

  2. Configure your LiveKit server URL and token in RoomContext (file: RoomContext.swift):

    url = "wss://<your-livekit-host>.livekit.cloud"
    token = "<your-jwt-token>"
    
  3. Open LiveKit_ARKit_MinimalSetupApp.swift.

  4. Choose one of the two lifecycle configurations:


A. Global shared instance (original example behavior)

RoomContextView(lifecycleMode: .globalSharedInstance)

→ Produces one reproducible leak after leaving the room.


B. New Room per join (perConnectionNewInstance)

RoomContextView(lifecycleMode: .perConnectionNewInstance)

→ Produces more than 24 leaks after rejoining a session.

In this mode:

  • connect() creates a fresh Room().
  • After disconnect(), the previous Room is replaced by a new placeholder Room
    (alternatively it could be set to nil, but that's not required for the repro).
  1. Run the application
  2. Join a session
  3. Activate the camera (top right corner)
  4. Leave the session
  5. Repeat joining a session

However, the previous Room instance is no longer referenced anywhere in the app.
Despite that, Instruments shows multiple LiveKit internal structures as leaked
(DataChannel / Async / Delegate / StateSync objects, etc.).

Mt-Perazim avatar Dec 09 '25 12:12 Mt-Perazim