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