...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package leakcheck
21
22 import (
23 "runtime"
24 "sort"
25 "strings"
26 "time"
27 )
28
29 var goroutinesToIgnore = []string{
30 "net/http/transport.go",
31 "net/http/h2_bundle.go",
32 "src/go.opencensus.io/stats/view/worker.go",
33 "testing.Main(",
34 "testing.tRunner(",
35 "testing.(*M).",
36 "runtime.goexit",
37 "created by runtime.gc",
38 "created by runtime/trace.Start",
39 "interestingGoroutines",
40 "runtime.MHeap_Scavenger",
41 "signal.signal_recv",
42 "sigterm.handler",
43 "runtime_mcall",
44 "(*loggingT).flushDaemon",
45 "goroutine in C code",
46 }
47
48
49
50
51 func RegisterIgnoreGoroutine(s string) {
52 goroutinesToIgnore = append(goroutinesToIgnore, s)
53 }
54
55 func ignore(g string) bool {
56 sl := strings.SplitN(g, "\n", 2)
57 if len(sl) != 2 {
58 return true
59 }
60 stack := strings.TrimSpace(sl[1])
61 if strings.HasPrefix(stack, "testing.RunTests") {
62 return true
63 }
64
65 if stack == "" {
66 return true
67 }
68
69 for _, s := range goroutinesToIgnore {
70 if strings.Contains(stack, s) {
71 return true
72 }
73 }
74
75 return false
76 }
77
78
79
80 func interestingGoroutines() (gs []string) {
81 buf := make([]byte, 2<<20)
82 buf = buf[:runtime.Stack(buf, true)]
83 for _, g := range strings.Split(string(buf), "\n\n") {
84 if !ignore(g) {
85 gs = append(gs, g)
86 }
87 }
88 sort.Strings(gs)
89 return
90 }
91
92
93
94 type Errorfer interface {
95 Errorf(format string, args ...interface{})
96 }
97
98 func check(efer Errorfer, timeout time.Duration) {
99
100
101 deadline := time.Now().Add(timeout)
102 var leaked []string
103 for time.Now().Before(deadline) {
104 if leaked = interestingGoroutines(); len(leaked) == 0 {
105 return
106 }
107 time.Sleep(50 * time.Millisecond)
108 }
109 for _, g := range leaked {
110 efer.Errorf("Leaked goroutine: %v", g)
111 }
112 }
113
114
115
116
117 func Check(efer Errorfer) {
118 check(efer, 10*time.Second)
119 }
120
View as plain text