Skip to content

Cancellation during screen share enable orphans track resources after service bind #985

Closed

Description

@davidliu

Summary

#983 fixes ScreenCaptureService staying bound when a coroutine is cancelled during ScreenCaptureConnection.connect(). A broader follow-up remains: LocalParticipant.setScreenShareEnabled / setTrackEnabled can still abandon a newly created screencast track on cancellation without calling stop() / dispose(), leaking WebRTC and capture resources. If cancel happens after a successful bind, the service binding is orphaned too.

Background

Screen share enable currently does roughly:

val track = createScreencastTrack(...)
track.startForegroundService(...) // suspend: bind + startForeground
track.startCapture()
if (!publishVideoTrack(track, ...)) {
    // cleanup only on publish failure
    track.stopCapture()
    track.stop()
    track.dispose()
}

setTrackEnabled has no try / finally around this path. Cleanup runs only when publishVideoTrack returns false. A CancellationException from startForegroundService or publishVideoTrack unwinds out of the function and drops the local track reference without cleanup.
After startForegroundService returns, only LocalScreencastVideoTrack.stop() → ScreenCaptureConnection.stop() releases the binding. Cancel after that point therefore also leaves ScreenCaptureService bound for the lifetime of the context.

What gets orphaned

Cancellation timing Service binding Other resources
After createScreencastTrack, during connect() (before it returns) Released by #983 Track / SurfaceTextureHelper / VideoSource / capturer / rtcTrack orphaned
After startForegroundService returns, before / during publishVideoTrack Orphaned Same as above; after startCapture(), active MediaProjection can keep running too

Expected behavior

Cancelling setScreenShareEnabled(true, ...) (or the surrounding scope) should always tear down any screencast track that was created but not successfully published: stop capture, unbind / stop the screen capture service if it was started, dispose WebRTC objects, and invoke any onStop callback as appropriate.

Suggested direction

  • Make ownership transfer cancellation-safe in LocalParticipant.setTrackEnabled for SCREEN_SHARE (and likely review the camera / mic enable paths for the same pattern): e.g. try / finally that disposes the unpublished track on cancel or failure.
  • Confirm LocalScreencastVideoTrack / SurfaceTextureHelper ownership so dispose is correct even when publish never completes.
  • Add tests that cancel at each suspend point (startForegroundService, publishVideoTrack) and assert resources are released (and the service is unbound when a bind had succeeded).

Related

Activity

  1. adrian-niculescu commented on Jul 27, 2026

    @adrian-niculescu
    Contributor

    @davidliu, shall I file a PR for this?

  2. davidliu commented on Jul 27, 2026

    @davidliu
    ContributorAuthor

    @adrian-niculescu yeah if you're able to, that'd be helpful.

  3. adrian-niculescu commented on Jul 27, 2026

    @adrian-niculescu
    Contributor

    @adrian-niculescu yeah if you're able to, that'd be helpful.

    Alright, I will, then. I was already playing with the followups I mentioned before.

    L.E. PR: #986

  4. added 9 commits that reference this issue on Jul 27, 2026
    90dc899
    62dabc8
    413face
    5efdced
    ba87fa2
    25fd158
    549ca9a
    f5218d0
    13364fd
  5. added a commit that references this issue on Aug 3, 2026
    7323be5
  6. added a commit that references this issue on Oct 3, 2026
    17cd9d2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions