diff --git a/internal/engine/goolom/lifecycle.go b/internal/engine/goolom/lifecycle.go index e340e91..316107f 100644 --- a/internal/engine/goolom/lifecycle.go +++ b/internal/engine/goolom/lifecycle.go @@ -197,8 +197,13 @@ func (s *Session) Close() error { if !alreadyClosing { leaveUID := uuid.New().String() leaveAck := s.registerAckWaiter(leaveUID) + // 2s matches our jitsi tear-down budget. The reason is the same: + // without giving the server time to register the leave, a + // back-to-back reconnection from the same client collides with a + // still-alive ghost participant on the SFU side and inherits + // stale media-flow state. if s.sendLeave(leaveUID) { - _ = s.waitForAck(leaveUID, leaveAck, 1500*time.Millisecond) + _ = s.waitForAck(leaveUID, leaveAck, 2*time.Second) } else { s.removeAckWaiter(leaveUID) } diff --git a/internal/engine/livekit/livekit.go b/internal/engine/livekit/livekit.go index 1d41c9d..fcc9749 100644 --- a/internal/engine/livekit/livekit.go +++ b/internal/engine/livekit/livekit.go @@ -188,6 +188,14 @@ func (s *Session) Close() error { if s.room != nil { s.unpublishLocalTracks() s.room.Disconnect() + // LiveKit's Disconnect() returns once the local SDK state + // is torn down, not when the server has actually evicted + // the participant. Without giving the signalling channel + // time to flush the LEAVE_REQUEST and the server to act on + // it, a back-to-back reconnect from the same identity in + // the same room sees a still-alive ghost participant on + // the SFU and inherits stale publication state. + time.Sleep(2 * time.Second) } close(s.sendQueue) s.wg.Wait()