Skip to content
Merged
Changes from all commits
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
22 changes: 17 additions & 5 deletions livekit-rtc/livekit/rtc/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from ._proto.rpc_pb2 import RpcMethodInvocationEvent
from ._utils import BroadcastQueue
from .e2ee import E2EEManager, E2EEOptions
from .log import logger
from .participant import (
LocalParticipant,
Participant,
Expand Down Expand Up @@ -821,11 +822,22 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None:
rparticipant._track_publications[rpublication.sid] = rpublication
self.emit("track_published", rpublication, rparticipant)
elif which == "track_unpublished":
rparticipant = self._remote_participants[event.track_unpublished.participant_identity]
rpublication = rparticipant._track_publications.pop(
event.track_unpublished.publication_sid
)
self.emit("track_unpublished", rpublication, rparticipant)
# The participant or publication may already have been removed by a
# racing disconnect or a duplicate event, so both lookups are done
# defensively and the emit is skipped when the entry is gone,
# mirroring the local_track_unpublished handler, instead of raising a
# KeyError that _listen_task logs as an error.
identity = event.track_unpublished.participant_identity
sid = event.track_unpublished.publication_sid
rp = self._remote_participants.get(identity)
if rp is not None:
rpub = rp._track_publications.pop(sid, None)
if rpub is not None:
self.emit("track_unpublished", rpub, rp)
else:
logger.debug("track_unpublished for untracked publication sid %s", sid)
else:
logger.debug("track_unpublished for untracked participant %s", identity)
elif which == "track_subscribed":
owned_track_info = event.track_subscribed.track
track_info = owned_track_info.info
Comment on lines 841 to 843

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.

🚩 Other remote-track event handlers still use hard dictionary lookups and may crash under the same race conditions

This PR defensively handles the case where track_unpublished arrives after the participant or publication has already been removed. However, the same race condition (participant disconnected before event is processed) could affect track_unsubscribed (room.py:857-858), track_subscribed (room.py:843-844), track_subscription_failed (room.py:867-868), track_muted (room.py:879), and track_unmuted (room.py:890), all of which still use hard [] lookups on self._remote_participants and track_publications. If the fix here is warranted, the same defensive pattern may be needed in those handlers to avoid KeyError crashes in _listen_task.

(Refers to lines 840-865)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Expand Down
Loading