Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9758be4

Browse files
committed
fix: Synchronize peer logging with a channel
We were depending on the close mutex to properly report connection state. This ensures the RTC connection is properly closed before returning.
1 parent 5367d93 commit 9758be4

File tree

1 file changed

+71
-36
lines changed

1 file changed

+71
-36
lines changed

peer/conn.go

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package peer
33
import (
44
"bytes"
55
"context"
6+
"fmt"
7+
68
"crypto/rand"
79
"crypto/sha256"
810
"io"
@@ -69,6 +71,8 @@ func newWithClientOrServer(servers []webrtc.ICEServer, client bool, opts *ConnOp
6971
rtc: rtc,
7072
offerrer: client,
7173
closed: make(chan struct{}),
74+
closedRTC: make(chan struct{}),
75+
closedICE: make(chan struct{}),
7276
dcOpenChannel: make(chan *webrtc.DataChannel),
7377
dcDisconnectChannel: make(chan struct{}),
7478
dcFailedChannel: make(chan struct{}),
@@ -109,6 +113,8 @@ type Conn struct {
109113
offerrer bool
110114

111115
closed chan struct{}
116+
closedRTC chan struct{}
117+
closedICE chan struct{}
112118
closeMutex sync.Mutex
113119
closeError error
114120

@@ -142,26 +148,22 @@ type Conn struct {
142148
func (c *Conn) init() error {
143149
c.rtc.OnNegotiationNeeded(c.negotiate)
144150
c.rtc.OnICEConnectionStateChange(func(iceConnectionState webrtc.ICEConnectionState) {
145-
// Close must be locked here otherwise log output can appear
146-
// after the connection has been closed.
147-
c.closeMutex.Lock()
148-
defer c.closeMutex.Unlock()
149-
if c.isClosed() {
150-
return
151-
}
152-
153151
c.opts.Logger.Debug(context.Background(), "ice connection state updated",
154152
slog.F("state", iceConnectionState))
153+
154+
if iceConnectionState == webrtc.ICEConnectionStateClosed {
155+
select {
156+
case <-c.closedICE:
157+
default:
158+
close(c.closedICE)
159+
}
160+
}
161+
})
162+
c.rtc.OnSignalingStateChange(func(signalState webrtc.SignalingState) {
163+
c.opts.Logger.Debug(context.Background(), "signal state updated",
164+
slog.F("state", signalState))
155165
})
156166
c.rtc.OnICEGatheringStateChange(func(iceGatherState webrtc.ICEGathererState) {
157-
// Close can't be locked here, because this is triggered
158-
// when close is called. It doesn't appear this get's
159-
// executed after close though, so it shouldn't cause
160-
// problems.
161-
if c.isClosed() {
162-
return
163-
}
164-
165167
c.opts.Logger.Debug(context.Background(), "ice gathering state updated",
166168
slog.F("state", iceGatherState))
167169
})
@@ -171,7 +173,7 @@ func (c *Conn) init() error {
171173
}
172174
json := iceCandidate.ToJSON()
173175
c.opts.Logger.Debug(context.Background(), "writing candidate to channel",
174-
slog.F("hash", sha256.Sum224([]byte(json.Candidate))),
176+
slog.F("hash", c.hashCandidate(json)),
175177
slog.F("length", len(json.Candidate)),
176178
)
177179
select {
@@ -188,19 +190,11 @@ func (c *Conn) init() error {
188190
default:
189191
}
190192
})
191-
c.rtc.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) {
192-
// Close must be locked here otherwise log output can appear
193-
// after the connection has been closed.
194-
c.closeMutex.Lock()
195-
defer c.closeMutex.Unlock()
196-
if c.isClosed() {
197-
return
198-
}
199-
193+
c.rtc.OnConnectionStateChange(func(peerConnectionState webrtc.PeerConnectionState) {
200194
c.opts.Logger.Debug(context.Background(), "rtc connection updated",
201-
slog.F("state", pcs))
195+
slog.F("state", peerConnectionState))
202196

203-
switch pcs {
197+
switch peerConnectionState {
204198
case webrtc.PeerConnectionStateDisconnected:
205199
for i := 0; i < int(c.dcDisconnectListeners.Load()); i++ {
206200
select {
@@ -216,6 +210,20 @@ func (c *Conn) init() error {
216210
}
217211
}
218212
}
213+
214+
if peerConnectionState == webrtc.PeerConnectionStateClosed {
215+
// Pion executes event handlers after close is called
216+
// on the RTC connection. This ensures our Close()
217+
// handler properly cleans up before returning.
218+
//
219+
// Pion can execute this multiple times, so we check
220+
// if it's open before closing.
221+
select {
222+
case <-c.closedRTC:
223+
default:
224+
close(c.closedRTC)
225+
}
226+
}
219227
})
220228
_, err := c.pingChannel()
221229
if err != nil {
@@ -275,17 +283,30 @@ func (c *Conn) pingEchoChannel() (*Channel, error) {
275283
return c.pingEchoChan, c.pingEchoError
276284
}
277285

286+
// Computes a hash of the ICE candidate. Used for debug logging.
287+
func (*Conn) hashCandidate(iceCandidate webrtc.ICECandidateInit) string {
288+
hash := sha256.Sum224([]byte(iceCandidate.Candidate))
289+
return fmt.Sprintf("%x", hash[:8])
290+
}
291+
278292
func (c *Conn) negotiate() {
279293
c.opts.Logger.Debug(context.Background(), "negotiating")
280294

281295
if c.offerrer {
296+
c.closeMutex.Lock()
282297
offer, err := c.rtc.CreateOffer(&webrtc.OfferOptions{})
298+
c.closeMutex.Unlock()
283299
if err != nil {
284300
_ = c.CloseWithError(xerrors.Errorf("create offer: %w", err))
285301
return
286302
}
287-
c.opts.Logger.Debug(context.Background(), "setting local description")
303+
c.opts.Logger.Debug(context.Background(), "setting local description", slog.F("closed", c.isClosed()))
304+
if c.isClosed() {
305+
return
306+
}
307+
c.closeMutex.Lock()
288308
err = c.rtc.SetLocalDescription(offer)
309+
c.closeMutex.Unlock()
289310
if err != nil {
290311
_ = c.CloseWithError(xerrors.Errorf("set local description: %w", err))
291312
return
@@ -305,7 +326,9 @@ func (c *Conn) negotiate() {
305326
}
306327

307328
c.opts.Logger.Debug(context.Background(), "setting remote description")
329+
c.closeMutex.Lock()
308330
err := c.rtc.SetRemoteDescription(remoteDescription)
331+
c.closeMutex.Unlock()
309332
if err != nil {
310333
_ = c.CloseWithError(xerrors.Errorf("set remote description (closed %v): %w", c.isClosed(), err))
311334
return
@@ -317,15 +340,17 @@ func (c *Conn) negotiate() {
317340
_ = c.CloseWithError(xerrors.Errorf("create answer: %w", err))
318341
return
319342
}
320-
c.opts.Logger.Debug(context.Background(), "setting local description")
343+
c.opts.Logger.Debug(context.Background(), "setting local description", slog.F("closed", c.isClosed()))
344+
if c.isClosed() {
345+
return
346+
}
347+
c.closeMutex.Lock()
321348
err = c.rtc.SetLocalDescription(answer)
349+
c.closeMutex.Unlock()
322350
if err != nil {
323351
_ = c.CloseWithError(xerrors.Errorf("set local description: %w", err))
324352
return
325353
}
326-
if c.isClosed() {
327-
return
328-
}
329354
select {
330355
case <-c.closed:
331356
return
@@ -339,9 +364,9 @@ func (c *Conn) negotiate() {
339364
c.pendingCandidatesMutex.Lock()
340365
defer c.pendingCandidatesMutex.Unlock()
341366
for _, pendingCandidate := range c.pendingRemoteCandidates {
342-
hash := sha256.Sum224([]byte(pendingCandidate.Candidate))
367+
//nolint:gosec
343368
c.opts.Logger.Debug(context.Background(), "flushing buffered remote candidate",
344-
slog.F("hash", hash),
369+
slog.F("hash", c.hashCandidate(pendingCandidate)),
345370
slog.F("length", len(pendingCandidate.Candidate)),
346371
)
347372
err := c.rtc.AddICECandidate(pendingCandidate)
@@ -368,7 +393,7 @@ func (c *Conn) AddRemoteCandidate(i webrtc.ICECandidateInit) error {
368393
c.pendingCandidatesMutex.Lock()
369394
defer c.pendingCandidatesMutex.Unlock()
370395
fields := []slog.Field{
371-
slog.F("hash", sha256.Sum224([]byte(i.Candidate))),
396+
slog.F("hash", c.hashCandidate(i)),
372397
slog.F("length", len(i.Candidate)),
373398
}
374399
if !c.pendingCandidatesFlushed {
@@ -542,5 +567,15 @@ func (c *Conn) CloseWithError(err error) error {
542567
// All logging, goroutines, and async functionality is cleaned up after this.
543568
c.dcClosedWaitGroup.Wait()
544569

570+
if c.rtc.ConnectionState() != webrtc.PeerConnectionStateNew {
571+
c.opts.Logger.Debug(context.Background(), "waiting for rtc connection close...")
572+
<-c.closedRTC
573+
}
574+
575+
if c.rtc.ICEConnectionState() != webrtc.ICEConnectionStateNew {
576+
c.opts.Logger.Debug(context.Background(), "waiting for ice connection close...")
577+
<-c.closedICE
578+
}
579+
545580
return err
546581
}

0 commit comments

Comments
 (0)