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

Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 3e699aa

Browse files
authored
fix: TCP connections leaking after RTC disconnects (#397)
* fix: TCP connections leaking after RTC disconnects * Fix race condition * Add switch * Lower tick time * Fix data race * Update Go version * Fix log err * Fix race
1 parent 4850266 commit 3e699aa

File tree

4 files changed

+62
-10
lines changed

4 files changed

+62
-10
lines changed

ci/image/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.16.3
1+
FROM golang:1.16.5
22

33
ENV GOFLAGS="-mod=readonly"
44
ENV CI=true

wsnet/conn.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ type dataChannelConn struct {
129129
}
130130

131131
func (c *dataChannelConn) init() {
132+
c.closedMutex.Lock()
133+
defer c.closedMutex.Unlock()
132134
c.sendMore = make(chan struct{}, 1)
133135
c.dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
134136
c.dc.OnBufferedAmountLow(func() {

wsnet/dial_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,43 @@ func TestDial(t *testing.T) {
266266
_ = conn.Close()
267267
assert.Equal(t, 1, dialer.activeConnections())
268268
})
269+
270+
t.Run("Close Listeners on Disconnect", func(t *testing.T) {
271+
t.Parallel()
272+
273+
tcpListener, err := net.Listen("tcp", "0.0.0.0:0")
274+
require.NoError(t, err)
275+
go func() {
276+
_, _ = tcpListener.Accept()
277+
}()
278+
279+
connectAddr, listenAddr := createDumbBroker(t)
280+
l, err := Listen(context.Background(), slogtest.Make(t, nil), listenAddr, "")
281+
require.NoError(t, err)
282+
283+
turnAddr, closeTurn := createTURNServer(t, ice.SchemeTypeTURN)
284+
dialer, err := DialWebsocket(context.Background(), connectAddr, &DialOptions{
285+
ICEServers: []webrtc.ICEServer{{
286+
URLs: []string{fmt.Sprintf("turn:%s", turnAddr)},
287+
Username: "example",
288+
Credential: testPass,
289+
CredentialType: webrtc.ICECredentialTypePassword,
290+
}},
291+
}, nil)
292+
require.NoError(t, err)
293+
294+
_, err = dialer.DialContext(context.Background(), "tcp", tcpListener.Addr().String())
295+
require.NoError(t, err)
296+
297+
closeTurn()
298+
299+
list := l.(*listener)
300+
assert.Eventually(t, func() bool {
301+
list.connClosersMut.Lock()
302+
defer list.connClosersMut.Unlock()
303+
return len(list.connClosers) == 0
304+
}, time.Second*15, time.Millisecond*100)
305+
})
269306
}
270307

271308
func BenchmarkThroughput(b *testing.B) {

wsnet/listen.go

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,11 @@ func (l *listener) dial(ctx context.Context) (<-chan error, error) {
159159
// so the cognitive overload linter has been disabled.
160160
// nolint:gocognit,nestif
161161
func (l *listener) negotiate(ctx context.Context, conn net.Conn) {
162+
id := atomic.AddInt64(&l.nextConnNumber, 1)
163+
ctx = slog.With(ctx, slog.F("conn_id", id))
164+
162165
var (
163166
err error
164-
id = atomic.AddInt64(&l.nextConnNumber, 1)
165167
decoder = json.NewDecoder(conn)
166168
rtc *webrtc.PeerConnection
167169
// If candidates are sent before an offer, we place them here.
@@ -171,7 +173,7 @@ func (l *listener) negotiate(ctx context.Context, conn net.Conn) {
171173
// Sends the error provided then closes the connection.
172174
// If RTC isn't connected, we'll close it.
173175
closeError = func(err error) {
174-
l.log.Warn(ctx, "negotiation error, closing connection", slog.Error(err))
176+
// l.log.Warn(ctx, "negotiation error, closing connection", slog.Error(err))
175177

176178
d, _ := json.Marshal(&BrokerMessage{
177179
Error: err.Error(),
@@ -187,7 +189,6 @@ func (l *listener) negotiate(ctx context.Context, conn net.Conn) {
187189
}
188190
)
189191

190-
ctx = slog.With(ctx, slog.F("conn_id", id))
191192
l.log.Info(ctx, "accepted new session from broker connection, negotiating")
192193

193194
for {
@@ -255,17 +256,26 @@ func (l *listener) negotiate(ctx context.Context, conn net.Conn) {
255256
return
256257
}
257258
rtc.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) {
258-
l.log.Debug(ctx, "connection state change", slog.F("state", pcs.String()))
259-
if pcs == webrtc.PeerConnectionStateConnecting {
259+
l.log.Info(ctx, "connection state change", slog.F("state", pcs.String()))
260+
switch pcs {
261+
case webrtc.PeerConnectionStateConnected:
260262
return
263+
case webrtc.PeerConnectionStateConnecting:
264+
// Safe to close the negotiating WebSocket.
265+
_ = conn.Close()
266+
return
267+
}
268+
269+
// Close connections opened when RTC was alive.
270+
l.connClosersMut.Lock()
271+
defer l.connClosersMut.Unlock()
272+
for _, connCloser := range l.connClosers {
273+
_ = connCloser.Close()
261274
}
262-
_ = conn.Close()
275+
l.connClosers = make([]io.Closer, 0)
263276
})
264277

265278
flushCandidates := proxyICECandidates(rtc, conn)
266-
l.connClosersMut.Lock()
267-
l.connClosers = append(l.connClosers, rtc)
268-
l.connClosersMut.Unlock()
269279
rtc.OnDataChannel(l.handle(ctx, msg))
270280

271281
l.log.Debug(ctx, "set remote description", slog.F("offer", *msg.Offer))
@@ -420,6 +430,9 @@ func (l *listener) handle(ctx context.Context, msg BrokerMessage) func(dc *webrt
420430
dc: dc,
421431
rw: rw,
422432
}
433+
l.connClosersMut.Lock()
434+
l.connClosers = append(l.connClosers, co)
435+
l.connClosersMut.Unlock()
423436
co.init()
424437
defer nc.Close()
425438
defer co.Close()

0 commit comments

Comments
 (0)