rtc: keep update cache eviction order when listing recent disconnects (#4975)

GetRecentlyDisconnectedParticipants walked the participant update cache
with Get, which moves each entry to the front of the LRU. Walking from
newest to oldest left the cache in reverse order, so after a reconnect
the next updates evicted the most recently updated participants first.
In a room where the subscriber has tracked 128 or more participants, a
participant that had just disconnected was then dropped from the cache
and a later reconnect did not report it as disconnected, and the
out-of-order version check was lost for recently updated participants.

Use Peek, which reads an entry without changing its recency.
This commit is contained in:
Raphael Fakhri
2026-10-11 01:09:04 +05:30
committed by GitHub
parent 6656cf194f
commit 09d69dabba
2 changed files with 38 additions and 1 deletions
+36
View File
@@ -305,6 +305,42 @@ func TestOutOfOrderUpdates(t *testing.T) {
require.Equal(t, "second update", sent.GetUpdate().Participants[0].Metadata)
}
func TestRecentlyDisconnectedKeepsUpdateOrder(t *testing.T) {
p := newParticipantForTest("test")
// fill the update cache (128 entries), the last update is a disconnect
const cacheSize = 128
for i := range cacheSize {
state := livekit.ParticipantInfo_ACTIVE
if i == cacheSize-1 {
state = livekit.ParticipantInfo_DISCONNECTED
}
require.NoError(t, p.SendParticipantUpdate([]*livekit.ParticipantInfo{{
Sid: fmt.Sprintf("PA_%d", i),
Identity: fmt.Sprintf("p%d", i),
Version: 1,
State: state,
}}))
}
disconnected := p.GetRecentlyDisconnectedParticipants()
require.Len(t, disconnected, 1)
require.Equal(t, "PA_127", disconnected[0].Sid)
// an update for a new participant evicts the least recently updated entry,
// not the disconnect that was just reported
require.NoError(t, p.SendParticipantUpdate([]*livekit.ParticipantInfo{{
Sid: "PA_new",
Identity: "new",
Version: 1,
State: livekit.ParticipantInfo_ACTIVE,
}}))
disconnected = p.GetRecentlyDisconnectedParticipants()
require.Len(t, disconnected, 1)
require.Equal(t, "PA_127", disconnected[0].Sid)
}
// after disconnection, things should continue to function and not panic
func TestDisconnectTiming(t *testing.T) {
t.Run("Negotiate doesn't panic after channel closed", func(t *testing.T) {
+2 -1
View File
@@ -268,7 +268,8 @@ func (p *ParticipantImpl) GetRecentlyDisconnectedParticipants() []*livekit.Parti
p.updateLock.Lock()
keys := p.updateCache.Keys()
for i := len(keys) - 1; i >= 0; i-- {
if info, ok := p.updateCache.Get(keys[i]); ok {
// Peek, as Get would move each entry to the front and reverse the eviction order
if info, ok := p.updateCache.Peek(keys[i]); ok {
if info.updatedAt.Before(lastSignalAt) {
continue
}