@@ -108,11 +108,12 @@ type Conn struct {
108
108
// Determines whether this connection will send the offer or the answer.
109
109
offerrer bool
110
110
111
- closed chan struct {}
112
- closedRTC chan struct {}
113
- closedICE chan struct {}
114
- closeMutex sync.Mutex
115
- closeError error
111
+ closed chan struct {}
112
+ closedRTC chan struct {}
113
+ closedICE chan struct {}
114
+ closedICEMutex sync.Mutex
115
+ closeMutex sync.Mutex
116
+ closeError error
116
117
117
118
dcOpenChannel chan * webrtc.DataChannel
118
119
dcDisconnectChannel chan struct {}
@@ -128,7 +129,6 @@ type Conn struct {
128
129
129
130
pendingCandidatesToSend []webrtc.ICECandidateInit
130
131
pendingCandidatesToSendMutex sync.Mutex
131
- pendingCandidatesFlushed bool
132
132
133
133
pingChannelID uint16
134
134
pingEchoChannelID uint16
@@ -155,6 +155,8 @@ func (c *Conn) init() error {
155
155
slog .F ("state" , iceConnectionState ))
156
156
157
157
if iceConnectionState == webrtc .ICEConnectionStateClosed {
158
+ c .closedICEMutex .Lock ()
159
+ defer c .closedICEMutex .Unlock ()
158
160
select {
159
161
case <- c .closedICE :
160
162
default :
@@ -169,14 +171,24 @@ func (c *Conn) init() error {
169
171
c .rtc .OnICEGatheringStateChange (func (iceGatherState webrtc.ICEGathererState ) {
170
172
c .opts .Logger .Debug (context .Background (), "ice gathering state updated" ,
171
173
slog .F ("state" , iceGatherState ))
174
+
175
+ if iceGatherState == webrtc .ICEGathererStateClosed {
176
+ c .closedICEMutex .Lock ()
177
+ defer c .closedICEMutex .Unlock ()
178
+ select {
179
+ case <- c .closedICE :
180
+ default :
181
+ close (c .closedICE )
182
+ }
183
+ }
172
184
})
173
185
c .rtc .OnICECandidate (func (iceCandidate * webrtc.ICECandidate ) {
174
186
if iceCandidate == nil {
175
187
return
176
188
}
177
189
c .pendingCandidatesToSendMutex .Lock ()
178
190
defer c .pendingCandidatesToSendMutex .Unlock ()
179
- if ! c . pendingCandidatesFlushed {
191
+ if c . rtc . RemoteDescription () == nil {
180
192
c .pendingCandidatesToSend = append (c .pendingCandidatesToSend , iceCandidate .ToJSON ())
181
193
c .opts .Logger .Debug (context .Background (), "buffering local candidate" )
182
194
return
@@ -197,6 +209,11 @@ func (c *Conn) init() error {
197
209
}
198
210
})
199
211
c .rtc .OnConnectionStateChange (func (peerConnectionState webrtc.PeerConnectionState ) {
212
+ if c .isClosed () {
213
+ return
214
+ }
215
+ // Pion executes this handler multiple times in a rare condition.
216
+ // This prevents logging from happening after close.
200
217
c .opts .Logger .Debug (context .Background (), "rtc connection updated" ,
201
218
slog .F ("state" , peerConnectionState ))
202
219
@@ -215,9 +232,7 @@ func (c *Conn) init() error {
215
232
default :
216
233
}
217
234
}
218
- }
219
-
220
- if peerConnectionState == webrtc .PeerConnectionStateClosed {
235
+ case webrtc .PeerConnectionStateClosed :
221
236
// Pion executes event handlers after close is called
222
237
// on the RTC connection. This ensures our Close()
223
238
// handler properly cleans up before returning.
@@ -226,7 +241,9 @@ func (c *Conn) init() error {
226
241
// if it's open before closing.
227
242
select {
228
243
case <- c .closedRTC :
244
+ c .opts .Logger .Debug (context .Background (), "closedRTC channel already closed" )
229
245
default :
246
+ c .opts .Logger .Debug (context .Background (), "closedRTC channel closing..." )
230
247
close (c .closedRTC )
231
248
}
232
249
}
@@ -248,14 +265,18 @@ func (c *Conn) init() error {
248
265
// uses trickle ICE by default. See: https://webrtchacks.com/trickle-ice/
249
266
func (c * Conn ) negotiate () {
250
267
c .opts .Logger .Debug (context .Background (), "negotiating" )
268
+ c .remoteSessionDescriptionMutex .Lock ()
269
+ defer c .remoteSessionDescriptionMutex .Unlock ()
251
270
252
271
if c .offerrer {
253
272
offer , err := c .rtc .CreateOffer (& webrtc.OfferOptions {})
254
273
if err != nil {
255
274
_ = c .CloseWithError (xerrors .Errorf ("create offer: %w" , err ))
256
275
return
257
276
}
277
+ c .closeMutex .Lock ()
258
278
err = c .rtc .SetLocalDescription (offer )
279
+ c .closeMutex .Unlock ()
259
280
if err != nil {
260
281
_ = c .CloseWithError (xerrors .Errorf ("set local description: %w" , err ))
261
282
return
@@ -266,25 +287,23 @@ func (c *Conn) negotiate() {
266
287
return
267
288
case c .localSessionDescriptionChannel <- offer :
268
289
}
290
+ c .opts .Logger .Debug (context .Background (), "sent offer" )
269
291
}
270
292
271
293
var sessionDescription webrtc.SessionDescription
294
+ c .opts .Logger .Debug (context .Background (), "awaiting remote description..." )
272
295
select {
273
296
case <- c .closed :
274
297
return
275
298
case sessionDescription = <- c .remoteSessionDescriptionChannel :
276
299
}
277
300
278
- // This prevents candidates from being added while
279
- // the remote description is being set.
280
- c .remoteSessionDescriptionMutex .Lock ()
281
301
c .opts .Logger .Debug (context .Background (), "setting remote description" )
282
302
err := c .rtc .SetRemoteDescription (sessionDescription )
283
303
if err != nil {
284
304
_ = c .CloseWithError (xerrors .Errorf ("set remote description (closed %v): %w" , c .isClosed (), err ))
285
305
return
286
306
}
287
- c .remoteSessionDescriptionMutex .Unlock ()
288
307
289
308
if ! c .offerrer {
290
309
answer , err := c .rtc .CreateAnswer (& webrtc.AnswerOptions {})
@@ -306,31 +325,44 @@ func (c *Conn) negotiate() {
306
325
return
307
326
case c .localSessionDescriptionChannel <- answer :
308
327
}
328
+ c .opts .Logger .Debug (context .Background (), "sent answer" )
309
329
}
310
330
311
- c .pendingCandidatesToSendMutex .Lock ()
312
- defer c .pendingCandidatesToSendMutex .Unlock ()
313
- for _ , pendingCandidate := range c .pendingCandidatesToSend {
314
- select {
315
- case <- c .closed :
316
- return
317
- case c .localCandidateChannel <- pendingCandidate :
331
+ go func () {
332
+ c .pendingCandidatesToSendMutex .Lock ()
333
+ defer c .pendingCandidatesToSendMutex .Unlock ()
334
+ for _ , pendingCandidate := range c .pendingCandidatesToSend {
335
+ select {
336
+ case <- c .closed :
337
+ return
338
+ case c .localCandidateChannel <- pendingCandidate :
339
+ }
340
+ c .opts .Logger .Debug (context .Background (), "flushed buffered local candidate" )
318
341
}
319
- c .opts .Logger .Debug (context .Background (), "flushed buffered local candidate" )
320
- }
321
- c .opts .Logger .Debug (context .Background (), "flushed buffered local candidates" ,
322
- slog .F ("count" , len (c .pendingCandidatesToSend )),
323
- )
324
- c .pendingCandidatesToSend = make ([]webrtc.ICECandidateInit , 0 )
325
- c .pendingCandidatesFlushed = true
342
+ c .opts .Logger .Debug (context .Background (), "flushed buffered local candidates" ,
343
+ slog .F ("count" , len (c .pendingCandidatesToSend )),
344
+ )
345
+ c .pendingCandidatesToSend = make ([]webrtc.ICECandidateInit , 0 )
346
+ }()
326
347
}
327
348
328
349
// AddRemoteCandidate adds a remote candidate to the RTC connection.
329
- func (c * Conn ) AddRemoteCandidate (i webrtc.ICECandidateInit ) error {
330
- c .remoteSessionDescriptionMutex .Lock ()
331
- defer c .remoteSessionDescriptionMutex .Unlock ()
332
- c .opts .Logger .Debug (context .Background (), "accepting candidate" , slog .F ("length" , len (i .Candidate )))
333
- return c .rtc .AddICECandidate (i )
350
+ func (c * Conn ) AddRemoteCandidate (i webrtc.ICECandidateInit ) {
351
+ if c .isClosed () {
352
+ return
353
+ }
354
+ go func () {
355
+ c .remoteSessionDescriptionMutex .Lock ()
356
+ defer c .remoteSessionDescriptionMutex .Unlock ()
357
+ if c .isClosed () {
358
+ return
359
+ }
360
+ c .opts .Logger .Debug (context .Background (), "accepting candidate" , slog .F ("length" , len (i .Candidate )))
361
+ err := c .rtc .AddICECandidate (i )
362
+ if err != nil {
363
+ _ = c .CloseWithError (xerrors .Errorf ("accept candidate: %w" , err ))
364
+ }
365
+ }()
334
366
}
335
367
336
368
// SetRemoteSessionDescription sets the remote description for the WebRTC connection.
@@ -528,7 +560,6 @@ func (c *Conn) CloseWithError(err error) error {
528
560
} else {
529
561
c .closeError = err
530
562
}
531
- close (c .closed )
532
563
533
564
if ch , _ := c .pingChannel (); ch != nil {
534
565
_ = ch .closeWithError (c .closeError )
@@ -538,10 +569,6 @@ func (c *Conn) CloseWithError(err error) error {
538
569
// closing an already closed connection isn't an issue for us.
539
570
_ = c .rtc .Close ()
540
571
541
- // Waits for all DataChannels to exit before officially labeling as closed.
542
- // All logging, goroutines, and async functionality is cleaned up after this.
543
- c .dcClosedWaitGroup .Wait ()
544
-
545
572
if c .rtc .ConnectionState () != webrtc .PeerConnectionStateNew {
546
573
c .opts .Logger .Debug (context .Background (), "waiting for rtc connection close..." )
547
574
<- c .closedRTC
@@ -552,5 +579,11 @@ func (c *Conn) CloseWithError(err error) error {
552
579
<- c .closedICE
553
580
}
554
581
582
+ // Waits for all DataChannels to exit before officially labeling as closed.
583
+ // All logging, goroutines, and async functionality is cleaned up after this.
584
+ c .dcClosedWaitGroup .Wait ()
585
+
586
+ close (c .closed )
587
+ c .opts .Logger .Debug (context .Background (), "closed" )
555
588
return err
556
589
}
0 commit comments