From 5573f30797b7901289d2e05c2920977a230bf1bb Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 3 Jul 2026 20:54:53 +0200 Subject: [PATCH 1/5] avoid KeyError when track is already unpublished --- livekit-rtc/livekit/rtc/room.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index 9498489a..3259bb31 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -821,11 +821,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 + rparticipant = self._remote_participants.get(identity) + if rparticipant is not None: + rpublication = rparticipant._track_publications.pop(sid, None) + if rpublication is not None: + self.emit("track_unpublished", rpublication, rparticipant) + else: + logging.debug("track_unpublished for untracked publication sid %s", sid) + else: + logging.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 From f23ce55faaeb752c1d5818e80ed0d74737421012 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 3 Jul 2026 20:59:01 +0200 Subject: [PATCH 2/5] fix types --- livekit-rtc/livekit/rtc/room.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index 3259bb31..5a32e19a 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -828,11 +828,11 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: # KeyError that _listen_task logs as an error. identity = event.track_unpublished.participant_identity sid = event.track_unpublished.publication_sid - rparticipant = self._remote_participants.get(identity) - if rparticipant is not None: - rpublication = rparticipant._track_publications.pop(sid, None) - if rpublication is not None: - self.emit("track_unpublished", rpublication, rparticipant) + rp = self._remote_participants.get(identity) + if rp is not None: + rpub = rparticipant._track_publications.pop(sid, None) + if rpub is not None: + self.emit("track_unpublished", rp, rpub) else: logging.debug("track_unpublished for untracked publication sid %s", sid) else: From 6ce21af9646d6071443122fdb6ec46c44421e7f9 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 3 Jul 2026 21:05:36 +0200 Subject: [PATCH 3/5] fix --- livekit-rtc/livekit/rtc/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index 5a32e19a..186deadb 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -830,7 +830,7 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: sid = event.track_unpublished.publication_sid rp = self._remote_participants.get(identity) if rp is not None: - rpub = rparticipant._track_publications.pop(sid, None) + rpub = rp._track_publications.pop(sid, None) if rpub is not None: self.emit("track_unpublished", rp, rpub) else: From dc8f9a5ffa19af1cd071533f8309de3f0dea9df7 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 3 Jul 2026 21:07:02 +0200 Subject: [PATCH 4/5] goddamnit --- livekit-rtc/livekit/rtc/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index 186deadb..a3579935 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -832,7 +832,7 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: if rp is not None: rpub = rp._track_publications.pop(sid, None) if rpub is not None: - self.emit("track_unpublished", rp, rpub) + self.emit("track_unpublished", rpub, rp) else: logging.debug("track_unpublished for untracked publication sid %s", sid) else: From 5884854e436e515796fb3dc7364af2a66eb0fa24 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Fri, 3 Jul 2026 21:26:03 +0200 Subject: [PATCH 5/5] logger --- livekit-rtc/livekit/rtc/room.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py index a3579935..0873dae0 100644 --- a/livekit-rtc/livekit/rtc/room.py +++ b/livekit-rtc/livekit/rtc/room.py @@ -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, @@ -834,9 +835,9 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None: if rpub is not None: self.emit("track_unpublished", rpub, rp) else: - logging.debug("track_unpublished for untracked publication sid %s", sid) + logger.debug("track_unpublished for untracked publication sid %s", sid) else: - logging.debug("track_unpublished for untracked participant %s", identity) + 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