Skip to content

Commit 9a23578

Browse files
authored
Merge pull request #29 from lawrencegripper/lg/ignorehttpkeepalive
Ignore HTTPKeepAlive in leaktest
2 parents b433bbd + c38c66b commit 9a23578

File tree

4 files changed

+144
-50
lines changed

4 files changed

+144
-50
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: go
22
go:
3-
- 1.7
43
- 1.8
54
- 1.9
65
- "1.10"

leaktest.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ func interestingGoroutine(g string) (*goroutine, error) {
4141
}
4242

4343
if stack == "" ||
44+
// Ignore HTTP keep alives
45+
strings.Contains(stack, ").readLoop(") ||
46+
strings.Contains(stack, ").writeLoop(") ||
4447
// Below are the stacks ignored by the upstream leaktest code.
4548
strings.Contains(stack, "testing.Main(") ||
4649
strings.Contains(stack, "testing.(*T).Run(") ||

leaktest_test.go

Lines changed: 111 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"net/http"
8+
"net/http/httptest"
79
"sync"
810
"testing"
911
"time"
@@ -19,60 +21,116 @@ func (tr *testReporter) Errorf(format string, args ...interface{}) {
1921
tr.msg = fmt.Sprintf(format, args...)
2022
}
2123

22-
var leakyFuncs = []func(){
23-
// Infinite for loop
24-
func() {
25-
for {
26-
time.Sleep(time.Second)
27-
}
28-
},
29-
// Select on a channel not referenced by other goroutines.
30-
func() {
31-
c := make(chan struct{})
32-
<-c
33-
},
34-
// Blocked select on channels not referenced by other goroutines.
35-
func() {
36-
c := make(chan struct{})
37-
c2 := make(chan struct{})
38-
select {
39-
case <-c:
40-
case c2 <- struct{}{}:
41-
}
42-
},
43-
// Blocking wait on sync.Mutex that isn't referenced by other goroutines.
44-
func() {
45-
var mu sync.Mutex
46-
mu.Lock()
47-
mu.Lock()
48-
},
49-
// Blocking wait on sync.RWMutex that isn't referenced by other goroutines.
50-
func() {
51-
var mu sync.RWMutex
52-
mu.RLock()
53-
mu.Lock()
54-
},
55-
func() {
56-
var mu sync.Mutex
57-
mu.Lock()
58-
c := sync.NewCond(&mu)
59-
c.Wait()
60-
},
61-
}
24+
// Client for the TestServer
25+
var testServer *httptest.Server
6226

6327
func TestCheck(t *testing.T) {
28+
leakyFuncs := []struct {
29+
f func()
30+
name string
31+
expectLeak bool
32+
}{
33+
{
34+
name: "Infinite for loop",
35+
expectLeak: true,
36+
f: func() {
37+
for {
38+
time.Sleep(time.Second)
39+
}
40+
},
41+
},
42+
{
43+
name: "Select on a channel not referenced by other goroutines.",
44+
expectLeak: true,
45+
f: func() {
46+
c := make(chan struct{})
47+
<-c
48+
},
49+
},
50+
{
51+
name: "Blocked select on channels not referenced by other goroutines.",
52+
expectLeak: true,
53+
f: func() {
54+
c := make(chan struct{})
55+
c2 := make(chan struct{})
56+
select {
57+
case <-c:
58+
case c2 <- struct{}{}:
59+
}
60+
},
61+
},
62+
{
63+
name: "Blocking wait on sync.Mutex that isn't referenced by other goroutines.",
64+
expectLeak: true,
65+
f: func() {
66+
var mu sync.Mutex
67+
mu.Lock()
68+
mu.Lock()
69+
},
70+
},
71+
{
72+
name: "Blocking wait on sync.RWMutex that isn't referenced by other goroutines.",
73+
expectLeak: true,
74+
f: func() {
75+
var mu sync.RWMutex
76+
mu.RLock()
77+
mu.Lock()
78+
},
79+
},
80+
{
81+
name: "HTTP Client with KeepAlive Disabled.",
82+
expectLeak: false,
83+
f: func() {
84+
tr := &http.Transport{
85+
DisableKeepAlives: true,
86+
}
87+
client := &http.Client{Transport: tr}
88+
_, err := client.Get(testServer.URL)
89+
if err != nil {
90+
t.Error(err)
91+
}
92+
},
93+
},
94+
{
95+
name: "HTTP Client with KeepAlive Enabled.",
96+
expectLeak: true,
97+
f: func() {
98+
tr := &http.Transport{
99+
DisableKeepAlives: false,
100+
}
101+
client := &http.Client{Transport: tr}
102+
_, err := client.Get(testServer.URL)
103+
if err != nil {
104+
t.Error(err)
105+
}
106+
},
107+
},
108+
}
109+
110+
// Start our keep alive server for keep alive tests
111+
ctx, cancel := context.WithCancel(context.Background())
112+
defer cancel()
113+
testServer = startKeepAliveEnabledServer(ctx)
114+
64115
// this works because the running goroutine is left running at the
65116
// start of the next test case - so the previous leaks don't affect the
66117
// check for the next one
67-
for i, fn := range leakyFuncs {
68-
checker := &testReporter{}
69-
snapshot := CheckTimeout(checker, time.Second)
70-
go fn()
71-
72-
snapshot()
73-
if !checker.failed {
74-
t.Errorf("didn't catch sleeping goroutine, test #%d", i)
75-
}
118+
for _, leakyTestcase := range leakyFuncs {
119+
120+
t.Run(leakyTestcase.name, func(t *testing.T) {
121+
checker := &testReporter{}
122+
snapshot := CheckTimeout(checker, time.Second)
123+
go leakyTestcase.f()
124+
125+
snapshot()
126+
127+
if !checker.failed && leakyTestcase.expectLeak {
128+
t.Error("didn't catch sleeping goroutine")
129+
}
130+
if checker.failed && !leakyTestcase.expectLeak {
131+
t.Error("got leak but didn't expect it")
132+
}
133+
})
76134
}
77135
}
78136

@@ -133,6 +191,10 @@ func TestInterestingGoroutine(t *testing.T) {
133191
stack: "goroutine 123 [running]:",
134192
err: errors.New(`error parsing stack: "goroutine 123 [running]:"`),
135193
},
194+
{
195+
stack: "goroutine 299 [IO wait]:\nnet/http.(*persistConn).readLoop(0xc420556240)",
196+
err: nil,
197+
},
136198
{
137199
stack: "goroutine 123 [running]:\ntesting.RunTests",
138200
err: nil,

leaktest_utils_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package leaktest
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"time"
8+
)
9+
10+
func index() http.Handler {
11+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
12+
w.WriteHeader(http.StatusOK)
13+
})
14+
}
15+
16+
func startKeepAliveEnabledServer(ctx context.Context) *httptest.Server {
17+
server := httptest.NewUnstartedServer(index())
18+
server.Config.ReadTimeout = 5 * time.Second
19+
server.Config.WriteTimeout = 10 * time.Second
20+
server.Config.IdleTimeout = 15 * time.Second
21+
server.Config.SetKeepAlivesEnabled(true)
22+
23+
server.Start()
24+
go func() {
25+
<-ctx.Done()
26+
server.Close()
27+
}()
28+
29+
return server
30+
}

0 commit comments

Comments
 (0)