options.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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. package goleak
  21. import (
  22. "strings"
  23. "time"
  24. "go.uber.org/goleak/internal/stack"
  25. )
  26. // Option lets users specify custom verifications.
  27. type Option interface {
  28. apply(*opts)
  29. }
  30. // We retry up to 20 times if we can't find the goroutine that
  31. // we are looking for. In between each attempt, we will sleep for
  32. // a short while to let any running goroutines complete.
  33. const _defaultRetries = 20
  34. type opts struct {
  35. filters []func(stack.Stack) bool
  36. maxRetries int
  37. maxSleep time.Duration
  38. cleanup func(int)
  39. }
  40. // implement apply so that opts struct itself can be used as
  41. // an Option.
  42. func (o *opts) apply(opts *opts) {
  43. opts.filters = o.filters
  44. opts.maxRetries = o.maxRetries
  45. opts.maxSleep = o.maxSleep
  46. opts.cleanup = o.cleanup
  47. }
  48. // optionFunc lets us easily write options without a custom type.
  49. type optionFunc func(*opts)
  50. func (f optionFunc) apply(opts *opts) { f(opts) }
  51. // IgnoreTopFunction ignores any goroutines where the specified function
  52. // is at the top of the stack. The function name should be fully qualified,
  53. // e.g., go.uber.org/goleak.IgnoreTopFunction
  54. func IgnoreTopFunction(f string) Option {
  55. return addFilter(func(s stack.Stack) bool {
  56. return s.FirstFunction() == f
  57. })
  58. }
  59. // Cleanup sets up a cleanup function that will be executed at the
  60. // end of the leak check.
  61. // When passed to [VerifyTestMain], the exit code passed to cleanupFunc
  62. // will be set to the exit code of TestMain.
  63. // When passed to [VerifyNone], the exit code will be set to 0.
  64. // This cannot be passed to [Find].
  65. func Cleanup(cleanupFunc func(exitCode int)) Option {
  66. return optionFunc(func(opts *opts) {
  67. opts.cleanup = cleanupFunc
  68. })
  69. }
  70. // IgnoreCurrent records all current goroutines when the option is created, and ignores
  71. // them in any future Find/Verify calls.
  72. func IgnoreCurrent() Option {
  73. excludeIDSet := map[int]bool{}
  74. for _, s := range stack.All() {
  75. excludeIDSet[s.ID()] = true
  76. }
  77. return addFilter(func(s stack.Stack) bool {
  78. return excludeIDSet[s.ID()]
  79. })
  80. }
  81. func maxSleep(d time.Duration) Option {
  82. return optionFunc(func(opts *opts) {
  83. opts.maxSleep = d
  84. })
  85. }
  86. func addFilter(f func(stack.Stack) bool) Option {
  87. return optionFunc(func(opts *opts) {
  88. opts.filters = append(opts.filters, f)
  89. })
  90. }
  91. func buildOpts(options ...Option) *opts {
  92. opts := &opts{
  93. maxRetries: _defaultRetries,
  94. maxSleep: 100 * time.Millisecond,
  95. }
  96. opts.filters = append(opts.filters,
  97. isTestStack,
  98. isSyscallStack,
  99. isStdLibStack,
  100. isTraceStack,
  101. )
  102. for _, option := range options {
  103. option.apply(opts)
  104. }
  105. return opts
  106. }
  107. func (o *opts) filter(s stack.Stack) bool {
  108. for _, filter := range o.filters {
  109. if filter(s) {
  110. return true
  111. }
  112. }
  113. return false
  114. }
  115. func (o *opts) retry(i int) bool {
  116. if i >= o.maxRetries {
  117. return false
  118. }
  119. d := time.Duration(int(time.Microsecond) << uint(i))
  120. if d > o.maxSleep {
  121. d = o.maxSleep
  122. }
  123. time.Sleep(d)
  124. return true
  125. }
  126. // isTestStack is a default filter installed to automatically skip goroutines
  127. // that the testing package runs while the user's tests are running.
  128. func isTestStack(s stack.Stack) bool {
  129. // Until go1.7, the main goroutine ran RunTests, which started
  130. // the test in a separate goroutine and waited for that test goroutine
  131. // to end by waiting on a channel.
  132. // Since go1.7, a separate goroutine is started to wait for signals.
  133. // T.Parallel is for parallel tests, which are blocked until all serial
  134. // tests have run with T.Parallel at the top of the stack.
  135. switch s.FirstFunction() {
  136. case "testing.RunTests", "testing.(*T).Run", "testing.(*T).Parallel":
  137. // In pre1.7 and post-1.7, background goroutines started by the testing
  138. // package are blocked waiting on a channel.
  139. return strings.HasPrefix(s.State(), "chan receive")
  140. }
  141. return false
  142. }
  143. func isSyscallStack(s stack.Stack) bool {
  144. // Typically runs in the background when code uses CGo:
  145. // https://github.com/golang/go/issues/16714
  146. return s.FirstFunction() == "runtime.goexit" && strings.HasPrefix(s.State(), "syscall")
  147. }
  148. func isStdLibStack(s stack.Stack) bool {
  149. // Importing os/signal starts a background goroutine.
  150. // The name of the function at the top has changed between versions.
  151. if f := s.FirstFunction(); f == "os/signal.signal_recv" || f == "os/signal.loop" {
  152. return true
  153. }
  154. // Using signal.Notify will start a runtime goroutine.
  155. return strings.Contains(s.Full(), "runtime.ensureSigM")
  156. }