diff --git a/pkg/sfu/downtrack.go b/pkg/sfu/downtrack.go index 68aaa6908..1ff4ac7aa 100644 --- a/pkg/sfu/downtrack.go +++ b/pkg/sfu/downtrack.go @@ -1440,15 +1440,24 @@ func (d *DownTrack) CloseWithFlush(flush bool, isEnding bool) { if flush { doneFlushing := d.writeBlankFrameRTP(RTPBlankFramesCloseSeconds, d.blankFramesGeneration.Inc()) + // The flush runs in its own goroutine (writeBlankFrameRTP) and is + // cancelled via blankFramesGeneration, so bindLock guards nothing + // during the wait. Release it: bindLock is a control-plane lock + // (Bind/SetConnected/ReceiverRestart), and holding it across the + // up-to-flushTimeout wait serializes all of those behind every + // close. isClosed is already set, so no other close can enter. + d.bindLock.Unlock() + // wait a limited time to flush timer := time.NewTimer(flushTimeout) - defer timer.Stop() - select { case <-doneFlushing: case <-timer.C: d.blankFramesGeneration.Inc() // in case flush is still running } + timer.Stop() + + d.bindLock.Lock() } d.params.Logger.Debugw("closing sender", "kind", d.kind) @@ -1755,6 +1764,10 @@ func (d *DownTrack) ReceiverRestart(rcvr TrackReceiver) { } d.bindLock.Lock() + if d.isClosed.Load() { + d.bindLock.Unlock() + return + } codec := d.codec.Load().(webrtc.RTPCodecCapability) d.bindLock.Unlock() @@ -2167,6 +2180,10 @@ func (d *DownTrack) handleRTCPRTX(bytes []byte) { func (d *DownTrack) SetConnected() { d.bindLock.Lock() + if d.isClosed.Load() { + d.bindLock.Unlock() + return + } if !d.connected.Swap(true) { d.onBindAndConnectedChange() }