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

Skip to content

Commit 09d8442

Browse files
committed
Properly close ICE gatherer
1 parent f20ce9d commit 09d8442

File tree

8 files changed

+141
-84
lines changed

8 files changed

+141
-84
lines changed

.github/workflows/coder.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ jobs:
158158
run:
159159
gotestsum --jsonfile="gotests.json" --packages="./..." --
160160
-covermode=atomic -coverprofile="gotests.coverage" -timeout=3m
161-
-count=3 -race -parallel=2
161+
-count=3 -race -short -parallel=2
162162

163163
- name: Test with PostgreSQL Database
164164
if: runner.os == 'Linux'

database/migrate_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ func TestMain(m *testing.M) {
2020
func TestMigrate(t *testing.T) {
2121
t.Parallel()
2222

23+
if testing.Short() {
24+
t.Skip()
25+
return
26+
}
27+
2328
t.Run("Once", func(t *testing.T) {
2429
t.Parallel()
2530
connection, closeFn, err := postgres.Open()

database/postgres/postgres_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ func TestMain(m *testing.M) {
2121
func TestPostgres(t *testing.T) {
2222
t.Parallel()
2323

24+
if testing.Short() {
25+
t.Skip()
26+
return
27+
}
28+
2429
connect, close, err := postgres.Open()
2530
require.NoError(t, err)
2631
defer close()

peer/channel.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,9 @@ func (c *Channel) init() {
141141
// A DataChannel can disconnect multiple times, so this needs to loop.
142142
for {
143143
select {
144-
case <-c.closed:
144+
case <-c.conn.closedRTC:
145145
// If this channel was closed, there's no need to close again.
146-
return
146+
err = c.conn.closeError
147147
case <-c.conn.Closed():
148148
// If the RTC connection closed with an error, this channel
149149
// should end with the same one.

peer/conn.go

Lines changed: 88 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,13 @@ type Conn struct {
108108
// Determines whether this connection will send the offer or the answer.
109109
offerrer bool
110110

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
116118

117119
dcOpenChannel chan *webrtc.DataChannel
118120
dcDisconnectChannel chan struct{}
@@ -128,7 +130,6 @@ type Conn struct {
128130

129131
pendingCandidatesToSend []webrtc.ICECandidateInit
130132
pendingCandidatesToSendMutex sync.Mutex
131-
pendingCandidatesFlushed bool
132133

133134
pingChannelID uint16
134135
pingEchoChannelID uint16
@@ -155,6 +156,8 @@ func (c *Conn) init() error {
155156
slog.F("state", iceConnectionState))
156157

157158
if iceConnectionState == webrtc.ICEConnectionStateClosed {
159+
c.closedICEMutex.Lock()
160+
defer c.closedICEMutex.Unlock()
158161
select {
159162
case <-c.closedICE:
160163
default:
@@ -169,24 +172,36 @@ func (c *Conn) init() error {
169172
c.rtc.OnICEGatheringStateChange(func(iceGatherState webrtc.ICEGathererState) {
170173
c.opts.Logger.Debug(context.Background(), "ice gathering state updated",
171174
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+
}
172185
})
173186
c.rtc.OnICECandidate(func(iceCandidate *webrtc.ICECandidate) {
174187
if iceCandidate == nil {
175188
return
176189
}
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+
}()
190205
})
191206
c.rtc.OnDataChannel(func(dc *webrtc.DataChannel) {
192207
select {
@@ -197,6 +212,11 @@ func (c *Conn) init() error {
197212
}
198213
})
199214
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.
200220
c.opts.Logger.Debug(context.Background(), "rtc connection updated",
201221
slog.F("state", peerConnectionState))
202222

@@ -215,18 +235,20 @@ func (c *Conn) init() error {
215235
default:
216236
}
217237
}
218-
}
219-
220-
if peerConnectionState == webrtc.PeerConnectionStateClosed {
238+
case webrtc.PeerConnectionStateClosed:
221239
// Pion executes event handlers after close is called
222240
// on the RTC connection. This ensures our Close()
223241
// handler properly cleans up before returning.
224242
//
225243
// Pion can execute this multiple times, so we check
226244
// if it's open before closing.
245+
c.closedRTCMutex.Lock()
246+
defer c.closedRTCMutex.Unlock()
227247
select {
228248
case <-c.closedRTC:
249+
c.opts.Logger.Debug(context.Background(), "closedRTC channel already closed")
229250
default:
251+
c.opts.Logger.Debug(context.Background(), "closedRTC channel closing...")
230252
close(c.closedRTC)
231253
}
232254
}
@@ -248,14 +270,18 @@ func (c *Conn) init() error {
248270
// uses trickle ICE by default. See: https://webrtchacks.com/trickle-ice/
249271
func (c *Conn) negotiate() {
250272
c.opts.Logger.Debug(context.Background(), "negotiating")
273+
c.remoteSessionDescriptionMutex.Lock()
274+
defer c.remoteSessionDescriptionMutex.Unlock()
251275

252276
if c.offerrer {
253277
offer, err := c.rtc.CreateOffer(&webrtc.OfferOptions{})
254278
if err != nil {
255279
_ = c.CloseWithError(xerrors.Errorf("create offer: %w", err))
256280
return
257281
}
282+
c.closeMutex.Lock()
258283
err = c.rtc.SetLocalDescription(offer)
284+
c.closeMutex.Unlock()
259285
if err != nil {
260286
_ = c.CloseWithError(xerrors.Errorf("set local description: %w", err))
261287
return
@@ -266,25 +292,23 @@ func (c *Conn) negotiate() {
266292
return
267293
case c.localSessionDescriptionChannel <- offer:
268294
}
295+
c.opts.Logger.Debug(context.Background(), "sent offer")
269296
}
270297

271298
var sessionDescription webrtc.SessionDescription
299+
c.opts.Logger.Debug(context.Background(), "awaiting remote description...")
272300
select {
273301
case <-c.closed:
274302
return
275303
case sessionDescription = <-c.remoteSessionDescriptionChannel:
276304
}
277305

278-
// This prevents candidates from being added while
279-
// the remote description is being set.
280-
c.remoteSessionDescriptionMutex.Lock()
281306
c.opts.Logger.Debug(context.Background(), "setting remote description")
282307
err := c.rtc.SetRemoteDescription(sessionDescription)
283308
if err != nil {
284309
_ = c.CloseWithError(xerrors.Errorf("set remote description (closed %v): %w", c.isClosed(), err))
285310
return
286311
}
287-
c.remoteSessionDescriptionMutex.Unlock()
288312

289313
if !c.offerrer {
290314
answer, err := c.rtc.CreateAnswer(&webrtc.AnswerOptions{})
@@ -306,31 +330,44 @@ func (c *Conn) negotiate() {
306330
return
307331
case c.localSessionDescriptionChannel <- answer:
308332
}
333+
c.opts.Logger.Debug(context.Background(), "sent answer")
309334
}
310335

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")
318346
}
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+
}()
326352
}
327353

328354
// 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+
}()
334371
}
335372

336373
// SetRemoteSessionDescription sets the remote description for the WebRTC connection.
@@ -528,7 +565,6 @@ func (c *Conn) CloseWithError(err error) error {
528565
} else {
529566
c.closeError = err
530567
}
531-
close(c.closed)
532568

533569
if ch, _ := c.pingChannel(); ch != nil {
534570
_ = ch.closeWithError(c.closeError)
@@ -538,10 +574,6 @@ func (c *Conn) CloseWithError(err error) error {
538574
// closing an already closed connection isn't an issue for us.
539575
_ = c.rtc.Close()
540576

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-
545577
if c.rtc.ConnectionState() != webrtc.PeerConnectionStateNew {
546578
c.opts.Logger.Debug(context.Background(), "waiting for rtc connection close...")
547579
<-c.closedRTC
@@ -552,5 +584,11 @@ func (c *Conn) CloseWithError(err error) error {
552584
<-c.closedICE
553585
}
554586

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")
555593
return err
556594
}

0 commit comments

Comments
 (0)