diff --git a/pkg/rtc/participant_internal_test.go b/pkg/rtc/participant_internal_test.go index 5827d3f9f..f3271eea4 100644 --- a/pkg/rtc/participant_internal_test.go +++ b/pkg/rtc/participant_internal_test.go @@ -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) { diff --git a/pkg/rtc/participant_signal.go b/pkg/rtc/participant_signal.go index 9202ebb91..315fbfe99 100644 --- a/pkg/rtc/participant_signal.go +++ b/pkg/rtc/participant_signal.go @@ -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 }