Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions livekit-rtc/livekit/rtc/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,8 +799,12 @@ async def unpublish_track(self, track_sid: str) -> None:
if cb.unpublish_track.error:
raise UnpublishTrackError(cb.unpublish_track.error)

publication = self._track_publications.pop(track_sid)
publication._track = None
# The local_track_unpublished room event may have already removed
# this publication from the dict (the FFI event and this async
# response race during teardown), so pop defensively.
publication = self._track_publications.pop(track_sid, None)
if publication is not None:
publication._track = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear the publication track after event-first unpublishes

When local_track_unpublished is processed before the async unpublish_track response, Room._on_room_event now removes the publication from _track_publications first. This defensive pop therefore returns None, skips the only _track = None cleanup, and leaves any LocalTrackPublication reference previously returned to application code reporting a stale non-None track even after await unpublish_track(...) completes. Preserve a reference for the async cleanup or clear the removed publication in the room-event path.

Useful? React with 👍 / 👎.

queue.task_done()
finally:
self._room_queue.unsubscribe(queue)
Expand Down
16 changes: 14 additions & 2 deletions livekit-rtc/livekit/rtc/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,9 +735,21 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None:
ltrack = lpublication.track
self.emit("local_track_published", lpublication, ltrack)
elif which == "local_track_unpublished":
# During teardown the publication may already have been removed
# from the participant's dict by LocalParticipant.unpublish_track
# (or by a previously processed event), so the SID can be gone by
# the time this event is dispatched. Pop defensively and skip the
# emit when it is no longer tracked, mirroring the remote
# track_unpublished and local_track_republished handlers, instead
# of raising a KeyError that _listen_task logs as an error.
sid = event.local_track_unpublished.publication_sid
lpublication = self.local_participant.track_publications[sid]
self.emit("local_track_unpublished", lpublication)
lpublication = self.local_participant._track_publications.pop(sid, None)
if lpublication is not None:
self.emit("local_track_unpublished", lpublication)
else:
logging.debug(
"local_track_unpublished for untracked publication sid %s", sid
)
elif which == "local_track_republished":
# The SDK auto-republished a local track during a full
# reconnect: the underlying Track (and its bound source) is
Expand Down
Loading