snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
This commit is contained in:
185
pkg/rtc/signalling/signallerasyncbase.go
Normal file
185
pkg/rtc/signalling/signallerasyncbase.go
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package signalling
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/livekit/protocol/logger"
|
||||
|
||||
"github.com/livekit/livekit-server/pkg/routing"
|
||||
"github.com/livekit/livekit-server/pkg/rtc/types"
|
||||
)
|
||||
|
||||
type signallerAsyncBaseParams struct {
|
||||
Logger logger.Logger
|
||||
// called when the connection opens, i. e. when messages held back for the
|
||||
// handshake may be sent
|
||||
OnHandshakeOpened func()
|
||||
}
|
||||
|
||||
// how long a resumed connection holds messages back waiting for its ReconnectResponse.
|
||||
// A path that resumes without sending one lets messages flow after this instead of
|
||||
// holding them back for the rest of the session.
|
||||
const handshakeWindow = 5 * time.Second
|
||||
|
||||
type signallerAsyncBase struct {
|
||||
signallerUnimplemented
|
||||
|
||||
params signallerAsyncBaseParams
|
||||
|
||||
resSinkMu sync.Mutex
|
||||
resSink routing.MessageSink
|
||||
// set while a resumed connection holds messages back waiting for its
|
||||
// ReconnectResponse, the timer opens the connection if none is written.
|
||||
// handshakeGeneration tells a timeout whether it belongs to the current wait, a
|
||||
// Stop cannot cancel a callback that has already started running.
|
||||
handshakePending bool
|
||||
handshakeGeneration uint32
|
||||
handshakeTimer *time.Timer
|
||||
}
|
||||
|
||||
func newSignallerAsyncBase(params signallerAsyncBaseParams) *signallerAsyncBase {
|
||||
return &signallerAsyncBase{
|
||||
params: params,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *signallerAsyncBase) SwapResponseSink(sink routing.MessageSink, reason types.SignallingCloseReason) {
|
||||
s.resSinkMu.Lock()
|
||||
oldSink := s.resSink
|
||||
s.resSink = sink
|
||||
// a resumed connection has to open with the ReconnectResponse, the client takes it
|
||||
// only as the first message it reads
|
||||
opened := false
|
||||
switch {
|
||||
case sink == nil:
|
||||
// the connection is gone, keep anything queued for the next one
|
||||
s.disarmHandshakeLocked()
|
||||
case reason == types.SignallingCloseReasonResume:
|
||||
s.armHandshakeLocked()
|
||||
default:
|
||||
opened = s.disarmHandshakeLocked()
|
||||
}
|
||||
s.resSinkMu.Unlock()
|
||||
|
||||
if opened {
|
||||
s.notifyHandshakeOpened()
|
||||
}
|
||||
|
||||
if oldSink != nil {
|
||||
if sink != nil {
|
||||
s.params.Logger.Debugw(
|
||||
"swapping signal connection",
|
||||
"reason", reason,
|
||||
"connID", oldSink.ConnectionID(),
|
||||
"newConnID", sink.ConnectionID(),
|
||||
)
|
||||
} else {
|
||||
s.params.Logger.Debugw(
|
||||
"closing signal connection",
|
||||
"reason", reason,
|
||||
"connID", oldSink.ConnectionID(),
|
||||
)
|
||||
}
|
||||
oldSink.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// HandshakePending is a plain read, so a caller can decide to hold a message back
|
||||
// while holding its own lock
|
||||
func (s *signallerAsyncBase) HandshakePending() bool {
|
||||
s.resSinkMu.Lock()
|
||||
defer s.resSinkMu.Unlock()
|
||||
|
||||
return s.handshakePending
|
||||
}
|
||||
|
||||
func (s *signallerAsyncBase) OpenHandshake() {
|
||||
s.resSinkMu.Lock()
|
||||
opened := s.disarmHandshakeLocked()
|
||||
s.resSinkMu.Unlock()
|
||||
|
||||
if opened {
|
||||
s.notifyHandshakeOpened()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *signallerAsyncBase) armHandshakeLocked() {
|
||||
s.stopHandshakeTimerLocked()
|
||||
|
||||
s.handshakePending = true
|
||||
s.handshakeGeneration++
|
||||
generation := s.handshakeGeneration
|
||||
// the connection opens on its own if nothing writes a ReconnectResponse, a path
|
||||
// that resumes without one should not hold messages back for the whole session
|
||||
s.handshakeTimer = time.AfterFunc(handshakeWindow, func() { s.onHandshakeTimeout(generation) })
|
||||
}
|
||||
|
||||
// disarmHandshakeLocked reports whether it opened a connection that was holding
|
||||
// messages back
|
||||
func (s *signallerAsyncBase) disarmHandshakeLocked() bool {
|
||||
s.stopHandshakeTimerLocked()
|
||||
|
||||
wasPending := s.handshakePending
|
||||
s.handshakePending = false
|
||||
// a timeout of the wait that just ended is stale
|
||||
s.handshakeGeneration++
|
||||
return wasPending
|
||||
}
|
||||
|
||||
func (s *signallerAsyncBase) stopHandshakeTimerLocked() {
|
||||
if s.handshakeTimer != nil {
|
||||
s.handshakeTimer.Stop()
|
||||
s.handshakeTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *signallerAsyncBase) onHandshakeTimeout(generation uint32) {
|
||||
s.resSinkMu.Lock()
|
||||
if generation != s.handshakeGeneration {
|
||||
// the wait this timeout was armed for has ended, a later one may be in progress
|
||||
s.resSinkMu.Unlock()
|
||||
return
|
||||
}
|
||||
opened := s.disarmHandshakeLocked()
|
||||
s.resSinkMu.Unlock()
|
||||
|
||||
if !opened {
|
||||
return
|
||||
}
|
||||
|
||||
s.params.Logger.Warnw("resumed connection did not open with a ReconnectResponse", nil)
|
||||
s.notifyHandshakeOpened()
|
||||
}
|
||||
|
||||
// notifyHandshakeOpened runs without resSinkMu held, the callback sends messages
|
||||
func (s *signallerAsyncBase) notifyHandshakeOpened() {
|
||||
if s.params.OnHandshakeOpened != nil {
|
||||
s.params.OnHandshakeOpened()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *signallerAsyncBase) GetResponseSink() routing.MessageSink {
|
||||
s.resSinkMu.Lock()
|
||||
defer s.resSinkMu.Unlock()
|
||||
return s.resSink
|
||||
}
|
||||
|
||||
// closes signal connection to notify client to resume/reconnect
|
||||
func (s *signallerAsyncBase) CloseSignalConnection(reason types.SignallingCloseReason) {
|
||||
s.SwapResponseSink(nil, reason)
|
||||
}
|
||||
Reference in New Issue
Block a user