Skip to content

Commit 35b4016

Browse files
authored
Merge pull request kubernetes#131170 from azych/fix_goroutine_leak
Fix goroutine leak
2 parents f8a6707 + ffe235d commit 35b4016

File tree

4 files changed

+93
-3
lines changed

4 files changed

+93
-3
lines changed

staging/src/k8s.io/kubelet/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/emicklei/go-restful/v3 v3.11.0
1111
github.com/gogo/protobuf v1.3.2
1212
github.com/stretchr/testify v1.10.0
13+
go.uber.org/goleak v1.3.0
1314
google.golang.org/grpc v1.68.1
1415
k8s.io/api v0.0.0
1516
k8s.io/apimachinery v0.0.0

staging/src/k8s.io/kubelet/go.sum

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package remotecommand
1818

1919
import (
20+
"context"
2021
"encoding/json"
2122
"errors"
2223
"fmt"
@@ -116,7 +117,7 @@ func createStreams(req *http.Request, w http.ResponseWriter, opts *Options, supp
116117

117118
if ctx.resizeStream != nil {
118119
ctx.resizeChan = make(chan remotecommand.TerminalSize)
119-
go handleResizeEvents(ctx.resizeStream, ctx.resizeChan)
120+
go handleResizeEvents(req.Context(), ctx.resizeStream, ctx.resizeChan)
120121
}
121122

122123
return ctx, true
@@ -409,7 +410,7 @@ WaitForStreams:
409410
// supportsTerminalResizing returns false because v1ProtocolHandler doesn't support it.
410411
func (*v1ProtocolHandler) supportsTerminalResizing() bool { return false }
411412

412-
func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalSize) {
413+
func handleResizeEvents(reqctx context.Context, stream io.Reader, channel chan<- remotecommand.TerminalSize) {
413414
defer runtime.HandleCrash()
414415
defer close(channel)
415416

@@ -419,7 +420,12 @@ func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalS
419420
if err := decoder.Decode(&size); err != nil {
420421
break
421422
}
422-
channel <- size
423+
select {
424+
case channel <- size:
425+
case <-reqctx.Done():
426+
// To prevent go routine leak.
427+
return
428+
}
423429
}
424430
}
425431

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package remotecommand
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"encoding/json"
23+
"io"
24+
"testing"
25+
26+
"github.com/stretchr/testify/require"
27+
"go.uber.org/goleak"
28+
29+
"k8s.io/client-go/tools/remotecommand"
30+
)
31+
32+
func TestHandleResizeEvents(t *testing.T) {
33+
var testTerminalSize remotecommand.TerminalSize
34+
rawTerminalSize, err := json.Marshal(&testTerminalSize)
35+
require.NoError(t, err)
36+
37+
testCases := []struct {
38+
name string
39+
resizeStreamData []byte
40+
cancelContext bool
41+
readFromChannel bool
42+
}{
43+
{
44+
name: "data attempted to be sent on the channel; channel not read; context canceled",
45+
resizeStreamData: rawTerminalSize,
46+
cancelContext: true,
47+
},
48+
{
49+
name: "data attempted to be sent on the channel; channel read; context not canceled",
50+
resizeStreamData: rawTerminalSize,
51+
readFromChannel: true,
52+
},
53+
{
54+
name: "no data attempted to be sent on the channel; context canceled",
55+
cancelContext: true,
56+
},
57+
{
58+
name: "no data attempted to be sent on the channel; context not canceled",
59+
},
60+
}
61+
for _, testCase := range testCases {
62+
t.Run(testCase.name, func(t *testing.T) {
63+
ctx, cancel := context.WithCancel(context.Background())
64+
connCtx := connectionContext{
65+
resizeStream: io.NopCloser(bytes.NewReader(testCase.resizeStreamData)),
66+
resizeChan: make(chan remotecommand.TerminalSize),
67+
}
68+
69+
go handleResizeEvents(ctx, connCtx.resizeStream, connCtx.resizeChan)
70+
if testCase.readFromChannel {
71+
gotTerminalSize := <-connCtx.resizeChan
72+
require.Equal(t, gotTerminalSize, testTerminalSize)
73+
}
74+
if testCase.cancelContext {
75+
cancel()
76+
}
77+
78+
goleak.VerifyNone(t)
79+
cancel()
80+
})
81+
}
82+
}

0 commit comments

Comments
 (0)