1
// Copyright (c) 2017 Uber Technologies, Inc.
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
// of this software and associated documentation files (the "Software"), to deal
5
// in the Software without restriction, including without limitation the rights
6
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
// copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in
11
// all copies or substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
// THE SOFTWARE.
20

21
package goleak
22

23
import (
24
	"strings"
25
	"time"
26

27
	"go.uber.org/goleak/internal/stack"
28
)
29

30
// Option lets users specify custom verifications.
31
type Option interface {
32
	apply(*opts)
33
}
34

35
type opts struct {
36
	filters  []func(stack.Stack) bool
37
	maxSleep time.Duration
38
	maxRetry time.Duration
39

40
	sleep func(time.Duration)
41
	now   func() time.Time
42
}
43

44
// optionFunc lets us easily write options without a custom type.
45
type optionFunc func(*opts)
46

47 2
func (f optionFunc) apply(opts *opts) { f(opts) }
48

49
// IgnoreTopFunction ignores any goroutines where the specified function
50
// is at the top of the stack. The function name should be fully qualified,
51
// e.g., go.uber.org/goleak.IgnoreTopFunction
52
func IgnoreTopFunction(f string) Option {
53 2
	return addFilter(func(s stack.Stack) bool {
54 2
		return s.FirstFunction() == f
55 2
	})
56
}
57

58
// IgnoreCurrent records all current goroutines when the option is created, and ignores
59
// them in any future Find/Verify calls.
60
func IgnoreCurrent() Option {
61 2
	excludeIDSet := map[int]bool{}
62
	for _, s := range stack.All() {
63 2
		excludeIDSet[s.ID()] = true
64
	}
65 2
	return addFilter(func(s stack.Stack) bool {
66 2
		return excludeIDSet[s.ID()]
67 2
	})
68
}
69

70
// MaxRetry time for the retry.
71
// We retry until d time if we can't find the goroutine that
72
// we are looking for. In between each attempt, we will sleep for
73
// a short while to let any running goroutines complete.
74
func MaxRetry(d time.Duration) Option {
75 2
	return optionFunc(func(opts *opts) {
76 2
		opts.maxRetry = d
77 2
	})
78
}
79

80
func maxSleep(d time.Duration) Option {
81 2
	return optionFunc(func(opts *opts) {
82 2
		opts.maxSleep = d
83 2
	})
84
}
85

86
func addFilter(f func(stack.Stack) bool) Option {
87 2
	return optionFunc(func(opts *opts) {
88 2
		opts.filters = append(opts.filters, f)
89 2
	})
90
}
91

92
func buildOpts(options ...Option) *opts {
93 2
	opts := &opts{
94 2
		maxRetry: 300 * time.Millisecond,
95 2
		maxSleep: 100 * time.Millisecond,
96 2
		sleep:    time.Sleep,
97 2
		now:      time.Now,
98
	}
99 2
	opts.filters = append(opts.filters,
100 2
		isTestStack,
101 2
		isSyscallStack,
102 2
		isStdLibStack,
103 2
		isTraceStack,
104 2
	)
105
	for _, option := range options {
106 2
		option.apply(opts)
107
	}
108 2
	return opts
109
}
110

111
func (vo *opts) filter(s stack.Stack) bool {
112
	for _, filter := range vo.filters {
113 2
		if filter(s) {
114 2
			return true
115
		}
116
	}
117 2
	return false
118
}
119

120
func (vo *opts) newRetry() func() bool {
121 2
	start := vo.now()
122 2
	sleep := 0 * time.Microsecond
123

124 2
	return func() bool {
125 2
		if sleep == 0 {
126 2
			sleep = time.Microsecond
127 2
			return true
128
		}
129

130 2
		if vo.now().Sub(start) >= vo.maxRetry {
131 2
			return false
132
		}
133

134 2
		vo.sleep(sleep)
135

136
		// simple backoff algorithm
137 2
		sleep *= 2
138 2
		if sleep > vo.maxSleep {
139 2
			sleep = vo.maxSleep
140
		}
141

142 2
		return true
143
	}
144
}
145

146
// isTestStack is a default filter installed to automatically skip goroutines
147
// that the testing package runs while the user's tests are running.
148
func isTestStack(s stack.Stack) bool {
149
	// Until go1.7, the main goroutine ran RunTests, which started
150
	// the test in a separate goroutine and waited for that test goroutine
151
	// to end by waiting on a channel.
152
	// Since go1.7, a separate goroutine is started to wait for signals.
153
	// T.Parallel is for parallel tests, which are blocked until all serial
154
	// tests have run with T.Parallel at the top of the stack.
155 2
	switch s.FirstFunction() {
156 2
	case "testing.RunTests", "testing.(*T).Run", "testing.(*T).Parallel":
157
		// In pre1.7 and post-1.7, background goroutines started by the testing
158
		// package are blocked waiting on a channel.
159 2
		return strings.HasPrefix(s.State(), "chan receive")
160
	}
161 2
	return false
162
}
163

164
func isSyscallStack(s stack.Stack) bool {
165
	// Typically runs in the background when code uses CGo:
166
	// https://github.com/golang/go/issues/16714
167 2
	return s.FirstFunction() == "runtime.goexit" && strings.HasPrefix(s.State(), "syscall")
168
}
169

170
func isStdLibStack(s stack.Stack) bool {
171
	// Importing os/signal starts a background goroutine.
172
	// The name of the function at the top has changed between versions.
173 2
	if f := s.FirstFunction(); f == "os/signal.signal_recv" || f == "os/signal.loop" {
174 2
		return true
175
	}
176

177
	// Using signal.Notify will start a runtime goroutine.
178 2
	return strings.Contains(s.Full(), "runtime.ensureSigM")
179
}
180

181
func isTraceStack(s stack.Stack) bool {
182 2
	if f := s.FirstFunction(); f != "runtime.goparkunlock" {
183 2
		return false
184
	}
185

186 1
	return strings.Contains(s.Full(), "runtime.ReadTrace")
187
}

Read our documentation on viewing source code .

Loading