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

Skip to content

Commit b493807

Browse files
committed
Properly close ICE gatherer
1 parent f20ce9d commit b493807

File tree

8 files changed

+124
-72
lines changed

8 files changed

+124
-72
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: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@ 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+
closedICE chan struct{}
114+
closedICEMutex sync.Mutex
115+
closeMutex sync.Mutex
116+
closeError error
116117

117118
dcOpenChannel chan *webrtc.DataChannel
118119
dcDisconnectChannel chan struct{}
@@ -128,7 +129,6 @@ type Conn struct {
128129

129130
pendingCandidatesToSend []webrtc.ICECandidateInit
130131
pendingCandidatesToSendMutex sync.Mutex
131-
pendingCandidatesFlushed bool
132132

133133
pingChannelID uint16
134134
pingEchoChannelID uint16
@@ -155,6 +155,8 @@ func (c *Conn) init() error {
155155
slog.F("state", iceConnectionState))
156156

157157
if iceConnectionState == webrtc.ICEConnectionStateClosed {
158+
c.closedICEMutex.Lock()
159+
defer c.closedICEMutex.Unlock()
158160
select {
159161
case <-c.closedICE:
160162
default:
@@ -169,14 +171,24 @@ func (c *Conn) init() error {
169171
c.rtc.OnICEGatheringStateChange(func(iceGatherState webrtc.ICEGathererState) {
170172
c.opts.Logger.Debug(context.Background(), "ice gathering state updated",
171173
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+
}
172184
})
173185
c.rtc.OnICECandidate(func(iceCandidate *webrtc.ICECandidate) {
174186
if iceCandidate == nil {
175187
return
176188
}
177189
c.pendingCandidatesToSendMutex.Lock()
178190
defer c.pendingCandidatesToSendMutex.Unlock()
179-
if !c.pendingCandidatesFlushed {
191+
if c.rtc.RemoteDescription() == nil {
180192
c.pendingCandidatesToSend = append(c.pendingCandidatesToSend, iceCandidate.ToJSON())
181193
c.opts.Logger.Debug(context.Background(), "buffering local candidate")
182194
return
@@ -197,6 +209,11 @@ func (c *Conn) init() error {
197209
}
198210
})
199211
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.
200217
c.opts.Logger.Debug(context.Background(), "rtc connection updated",
201218
slog.F("state", peerConnectionState))
202219

@@ -215,9 +232,7 @@ func (c *Conn) init() error {
215232
default:
216233
}
217234
}
218-
}
219-
220-
if peerConnectionState == webrtc.PeerConnectionStateClosed {
235+
case webrtc.PeerConnectionStateClosed:
221236
// Pion executes event handlers after close is called
222237
// on the RTC connection. This ensures our Close()
223238
// handler properly cleans up before returning.
@@ -226,7 +241,9 @@ func (c *Conn) init() error {
226241
// if it's open before closing.
227242
select {
228243
case <-c.closedRTC:
244+
c.opts.Logger.Debug(context.Background(), "closedRTC channel already closed")
229245
default:
246+
c.opts.Logger.Debug(context.Background(), "closedRTC channel closing...")
230247
close(c.closedRTC)
231248
}
232249
}
@@ -248,14 +265,18 @@ func (c *Conn) init() error {
248265
// uses trickle ICE by default. See: https://webrtchacks.com/trickle-ice/
249266
func (c *Conn) negotiate() {
250267
c.opts.Logger.Debug(context.Background(), "negotiating")
268+
c.remoteSessionDescriptionMutex.Lock()
269+
defer c.remoteSessionDescriptionMutex.Unlock()
251270

252271
if c.offerrer {
253272
offer, err := c.rtc.CreateOffer(&webrtc.OfferOptions{})
254273
if err != nil {
255274
_ = c.CloseWithError(xerrors.Errorf("create offer: %w", err))
256275
return
257276
}
277+
c.closeMutex.Lock()
258278
err = c.rtc.SetLocalDescription(offer)
279+
c.closeMutex.Unlock()
259280
if err != nil {
260281
_ = c.CloseWithError(xerrors.Errorf("set local description: %w", err))
261282
return
@@ -266,25 +287,23 @@ func (c *Conn) negotiate() {
266287
return
267288
case c.localSessionDescriptionChannel <- offer:
268289
}
290+
c.opts.Logger.Debug(context.Background(), "sent offer")
269291
}
270292

271293
var sessionDescription webrtc.SessionDescription
294+
c.opts.Logger.Debug(context.Background(), "awaiting remote description...")
272295
select {
273296
case <-c.closed:
274297
return
275298
case sessionDescription = <-c.remoteSessionDescriptionChannel:
276299
}
277300

278-
// This prevents candidates from being added while
279-
// the remote description is being set.
280-
c.remoteSessionDescriptionMutex.Lock()
281301
c.opts.Logger.Debug(context.Background(), "setting remote description")
282302
err := c.rtc.SetRemoteDescription(sessionDescription)
283303
if err != nil {
284304
_ = c.CloseWithError(xerrors.Errorf("set remote description (closed %v): %w", c.isClosed(), err))
285305
return
286306
}
287-
c.remoteSessionDescriptionMutex.Unlock()
288307

289308
if !c.offerrer {
290309
answer, err := c.rtc.CreateAnswer(&webrtc.AnswerOptions{})
@@ -306,31 +325,44 @@ func (c *Conn) negotiate() {
306325
return
307326
case c.localSessionDescriptionChannel <- answer:
308327
}
328+
c.opts.Logger.Debug(context.Background(), "sent answer")
309329
}
310330

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")
318341
}
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+
}()
326347
}
327348

328349
// 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+
}()
334366
}
335367

336368
// SetRemoteSessionDescription sets the remote description for the WebRTC connection.
@@ -528,7 +560,6 @@ func (c *Conn) CloseWithError(err error) error {
528560
} else {
529561
c.closeError = err
530562
}
531-
close(c.closed)
532563

533564
if ch, _ := c.pingChannel(); ch != nil {
534565
_ = ch.closeWithError(c.closeError)
@@ -538,10 +569,6 @@ func (c *Conn) CloseWithError(err error) error {
538569
// closing an already closed connection isn't an issue for us.
539570
_ = c.rtc.Close()
540571

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-
545572
if c.rtc.ConnectionState() != webrtc.PeerConnectionStateNew {
546573
c.opts.Logger.Debug(context.Background(), "waiting for rtc connection close...")
547574
<-c.closedRTC
@@ -552,5 +579,11 @@ func (c *Conn) CloseWithError(err error) error {
552579
<-c.closedICE
553580
}
554581

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")
555588
return err
556589
}

0 commit comments

Comments
 (0)