mirror of
https://github.com/livekit/livekit.git
synced 2026-10-22 17:32:23 +00:00
rtc: record a room's departure when the participant leaves it (#4913)
* rtc: record a room's departure when the participant leaves it RemoveParticipant removed the participant from the room, closed it, and only then recorded leftAt. CloseIfEmpty runs every second, so a tick landing during the close saw an empty room with a stale or zero departure time and closed it at once: with no departure recorded it compares against the room's creation time and empty_timeout, and with an earlier departure it measures the grace period from that older timestamp. Either way departure_timeout is skipped. Record the departure where the participant is removed, under the lock that already covers it. * rtc: drop the removal wait from the departure timeout test The close signal already pins the interleaving, and the room's close state is decided by the CloseIfEmpty call itself.
This commit is contained in:
1 parent
6cfd19bd9b
commit
5cd0f37342
2 files changed
+53
-2
No files matched your search
+3
-2
@@ -1431,6 +1431,9 @@ func (r *Room) RemoveParticipant(
|
||||
delete(r.hasPublished, identity)
|
||||
delete(r.launchedTrackEgresses, identity)
|
||||
delete(r.agentParticpants, identity)
|
||||
// recorded while the participant is being removed, not after it is closed:
|
||||
// the room is already empty to CloseIfEmpty, which runs every second
|
||||
r.leftAt.Store(time.Now().Unix())
|
||||
if !p.Hidden() {
|
||||
r.protoRoom.NumParticipants--
|
||||
}
|
||||
@@ -1491,8 +1494,6 @@ func (r *Room) RemoveParticipant(
|
||||
// close participant as well
|
||||
_ = p.Close(true, reason, false)
|
||||
|
||||
r.leftAt.Store(time.Now().Unix())
|
||||
|
||||
if sendUpdates {
|
||||
if r.onParticipantChanged != nil {
|
||||
r.onParticipantChanged(p)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/livekit/protocol/auth/authfakes"
|
||||
@@ -402,6 +403,55 @@ func TestRoomClosure(t *testing.T) {
|
||||
require.Equal(t, ErrRoomClosed, rm.Join(p, nil, nil, iceServersForRoom))
|
||||
})
|
||||
|
||||
// the idle check runs every second and can land while the last participant
|
||||
// is still closing, when the room is empty but its departure has not been
|
||||
// recorded yet
|
||||
closeDuringRemove := func(t *testing.T, priorDeparture bool) bool {
|
||||
rm := newRoomWithParticipants(t, testRoomOpts{num: 1})
|
||||
var isClosed atomic.Bool
|
||||
rm.OnClose(func(types.RoomCloseReason) {
|
||||
isClosed.Store(true)
|
||||
})
|
||||
|
||||
rm.lock.Lock()
|
||||
rm.protoRoom.DepartureTimeout = 20
|
||||
if priorDeparture {
|
||||
// someone left earlier in the session, so a departure is recorded
|
||||
rm.protoRoom.CreationTime = time.Now().Unix()
|
||||
rm.leftAt.Store(time.Now().Add(-time.Minute).Unix())
|
||||
} else {
|
||||
// older than the empty timeout, as a room in a call for a while is
|
||||
rm.protoRoom.CreationTime = time.Now().Add(-10 * time.Minute).Unix()
|
||||
}
|
||||
rm.lock.Unlock()
|
||||
|
||||
p := rm.GetParticipants()[0].(*typesfakes.FakeLocalParticipant)
|
||||
closing := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
p.CloseStub = func(bool, types.ParticipantCloseReason, bool) error {
|
||||
close(closing)
|
||||
<-release
|
||||
return nil
|
||||
}
|
||||
|
||||
go rm.RemoveParticipant(p.Identity(), p.ID(), types.ParticipantCloseReasonClientRequestLeave)
|
||||
|
||||
<-closing
|
||||
require.Empty(t, rm.GetParticipants())
|
||||
rm.CloseIfEmpty()
|
||||
close(release)
|
||||
|
||||
return isClosed.Load()
|
||||
}
|
||||
|
||||
t.Run("room does not close while the last participant is closing", func(t *testing.T) {
|
||||
require.False(t, closeDuringRemove(t, false))
|
||||
})
|
||||
|
||||
t.Run("room does not close while the last participant is closing, after an earlier departure", func(t *testing.T) {
|
||||
require.False(t, closeDuringRemove(t, true))
|
||||
})
|
||||
|
||||
t.Run("room does not close before empty timeout", func(t *testing.T) {
|
||||
rm := newRoomWithParticipants(t, testRoomOpts{num: 0})
|
||||
isClosed := false
|
||||
|
||||
Reference in new issue
Block a user