fix: goroutine leak when timeout
This commit is contained in:
parent
ead2fce2d3
commit
c9e5592a6e
1
go.mod
1
go.mod
@ -9,6 +9,7 @@ require (
|
|||||||
github.com/onsi/gomega v1.37.0
|
github.com/onsi/gomega v1.37.0
|
||||||
github.com/pborman/uuid v1.2.1
|
github.com/pborman/uuid v1.2.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
go.uber.org/goleak v1.3.0
|
||||||
golang.org/x/net v0.39.0
|
golang.org/x/net v0.39.0
|
||||||
google.golang.org/grpc v1.72.0
|
google.golang.org/grpc v1.72.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
|
|||||||
@ -272,7 +272,7 @@ type TimeoutFunc func() (err error)
|
|||||||
// WaitUntilTimeout waits for the exec function to complete or return timeout error
|
// WaitUntilTimeout waits for the exec function to complete or return timeout error
|
||||||
func WaitUntilTimeout(timeout time.Duration, execFunc ExecFunc, timeoutFunc TimeoutFunc) error {
|
func WaitUntilTimeout(timeout time.Duration, execFunc ExecFunc, timeoutFunc TimeoutFunc) error {
|
||||||
// Create a channel to receive the result of the exec function
|
// Create a channel to receive the result of the exec function
|
||||||
done := make(chan bool)
|
done := make(chan bool, 1)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Start the exec function in a goroutine
|
// Start the exec function in a goroutine
|
||||||
|
|||||||
@ -23,6 +23,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/goleak"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -449,6 +451,7 @@ func TestRemoveEmptyDirs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitUntilTimeout(t *testing.T) {
|
func TestWaitUntilTimeout(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
@ -494,8 +497,11 @@ func TestWaitUntilTimeout(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
err := WaitUntilTimeout(test.timeout, test.execFunc, test.timeoutFunc)
|
err := WaitUntilTimeout(test.timeout, test.execFunc, test.timeoutFunc)
|
||||||
if err != nil && (err.Error() != test.expectedErr.Error()) {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v, expected error: %v", err, test.expectedErr)
|
time.Sleep(2 * time.Second)
|
||||||
|
if err.Error() != test.expectedErr.Error() {
|
||||||
|
t.Errorf("unexpected error: %v, expected error: %v", err, test.expectedErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
vendor/go.uber.org/goleak/.gitignore
generated
vendored
Normal file
5
vendor/go.uber.org/goleak/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
vendor/
|
||||||
|
/bin
|
||||||
|
/lint.log
|
||||||
|
/cover.out
|
||||||
|
/cover.html
|
||||||
28
vendor/go.uber.org/goleak/.golangci.yml
generated
vendored
Normal file
28
vendor/go.uber.org/goleak/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
output:
|
||||||
|
# Make output more digestible with quickfix in vim/emacs/etc.
|
||||||
|
sort-results: true
|
||||||
|
print-issued-lines: false
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- gofumpt
|
||||||
|
- nolintlint
|
||||||
|
- revive
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
# These govet checks are disabled by default, but they're useful.
|
||||||
|
enable:
|
||||||
|
- niliness
|
||||||
|
- reflectvaluecompare
|
||||||
|
- sortslice
|
||||||
|
- unusedwrite
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# Print all issues reported by all linters.
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
|
|
||||||
|
# Don't ignore some of the issues that golangci-lint considers okay.
|
||||||
|
# This includes documenting all exported entities.
|
||||||
|
exclude-use-default: false
|
||||||
74
vendor/go.uber.org/goleak/CHANGELOG.md
generated
vendored
Normal file
74
vendor/go.uber.org/goleak/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.3.0]
|
||||||
|
### Fixed
|
||||||
|
- Built-in ignores now match function names more accurately.
|
||||||
|
They will no longer ignore stacks because of file names
|
||||||
|
that look similar to function names. (#112)
|
||||||
|
### Added
|
||||||
|
- Add an `IgnoreAnyFunction` option to ignore stack traces
|
||||||
|
that have the provided function anywhere in the stack. (#113)
|
||||||
|
- Ignore `testing.runFuzzing` and `testing.runFuzzTests` alongside
|
||||||
|
other already-ignored test functions (`testing.RunTests`, etc). (#105)
|
||||||
|
### Changed
|
||||||
|
- Miscellaneous CI-related fixes. (#103, #108, #114)
|
||||||
|
|
||||||
|
[1.3.0]: https://github.com/uber-go/goleak/compare/v1.2.1...v1.3.0
|
||||||
|
|
||||||
|
## [1.2.1]
|
||||||
|
### Changed
|
||||||
|
- Drop golang/x/lint dependency.
|
||||||
|
|
||||||
|
[1.2.1]: https://github.com/uber-go/goleak/compare/v1.2.0...v1.2.1
|
||||||
|
|
||||||
|
## [1.2.0]
|
||||||
|
### Added
|
||||||
|
- Add Cleanup option that can be used for registering cleanup callbacks. (#78)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Mark VerifyNone as a test helper. (#75)
|
||||||
|
|
||||||
|
Thanks to @tallclair for their contribution to this release.
|
||||||
|
|
||||||
|
[1.2.0]: https://github.com/uber-go/goleak/compare/v1.1.12...v1.2.0
|
||||||
|
|
||||||
|
## [1.1.12]
|
||||||
|
### Fixed
|
||||||
|
- Fixed logic for ignoring trace related goroutines on Go versions 1.16 and above.
|
||||||
|
|
||||||
|
[1.1.12]: https://github.com/uber-go/goleak/compare/v1.1.11...v1.1.12
|
||||||
|
|
||||||
|
## [1.1.11]
|
||||||
|
### Fixed
|
||||||
|
- Documentation fix on how to test.
|
||||||
|
- Update dependency on stretchr/testify to v1.7.0. (#59)
|
||||||
|
- Update dependency on golang.org/x/tools to address CVE-2020-14040. (#62)
|
||||||
|
|
||||||
|
[1.1.11]: https://github.com/uber-go/goleak/compare/v1.1.10...v1.1.11
|
||||||
|
|
||||||
|
## [1.1.10]
|
||||||
|
### Added
|
||||||
|
- [#49]: Add option to ignore current goroutines, which checks for any additional leaks and allows for incremental adoption of goleak in larger projects.
|
||||||
|
|
||||||
|
Thanks to @denis-tingajkin for their contributions to this release.
|
||||||
|
|
||||||
|
[#49]: https://github.com/uber-go/goleak/pull/49
|
||||||
|
[1.1.10]: https://github.com/uber-go/goleak/compare/v1.0.0...v1.1.10
|
||||||
|
|
||||||
|
## [1.0.0]
|
||||||
|
### Changed
|
||||||
|
- Migrate to Go modules.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Ignore trace related goroutines that cause false positives with -trace.
|
||||||
|
|
||||||
|
[1.0.0]: https://github.com/uber-go/goleak/compare/v0.10.0...v1.0.0
|
||||||
|
|
||||||
|
## [0.10.0]
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
[0.10.0]: https://github.com/uber-go/goleak/compare/v0.10.0...HEAD
|
||||||
21
vendor/go.uber.org/goleak/LICENSE
generated
vendored
Normal file
21
vendor/go.uber.org/goleak/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
45
vendor/go.uber.org/goleak/Makefile
generated
vendored
Normal file
45
vendor/go.uber.org/goleak/Makefile
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Directory containing the Makefile.
|
||||||
|
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
|
export GOBIN = $(PROJECT_ROOT)/bin
|
||||||
|
export PATH := $(GOBIN):$(PATH)
|
||||||
|
|
||||||
|
GO_FILES = $(shell find . \
|
||||||
|
-path '*/.*' -prune -o \
|
||||||
|
'(' -type f -a -name '*.go' ')' -print)
|
||||||
|
|
||||||
|
# Additional test flags.
|
||||||
|
TEST_FLAGS ?=
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: lint build test
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: golangci-lint tidy-lint
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
go build ./...
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test -v -race ./...
|
||||||
|
go test -v -trace=/dev/null .
|
||||||
|
|
||||||
|
.PHONY: cover
|
||||||
|
cover:
|
||||||
|
go test -race -coverprofile=cover.out -coverpkg=./... ./...
|
||||||
|
go tool cover -html=cover.out -o cover.html
|
||||||
|
|
||||||
|
.PHONY: golangci-lint
|
||||||
|
golangci-lint:
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
.PHONY: tidy
|
||||||
|
tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
.PHONY: tidy-lint
|
||||||
|
tidy-lint:
|
||||||
|
go mod tidy
|
||||||
|
git diff --exit-code -- go.mod go.sum
|
||||||
74
vendor/go.uber.org/goleak/README.md
generated
vendored
Normal file
74
vendor/go.uber.org/goleak/README.md
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# goleak [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
|
||||||
|
|
||||||
|
Goroutine leak detector to help avoid Goroutine leaks.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can use `go get` to get the latest version:
|
||||||
|
|
||||||
|
`go get -u go.uber.org/goleak`
|
||||||
|
|
||||||
|
`goleak` also supports semver releases.
|
||||||
|
|
||||||
|
Note that go-leak only [supports][release] the two most recent minor versions of Go.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
To verify that there are no unexpected goroutines running at the end of a test:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestA(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
|
|
||||||
|
// test logic here.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of checking for leaks at the end of every test, `goleak` can also be run
|
||||||
|
at the end of every test package by creating a `TestMain` function for your
|
||||||
|
package:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
goleak.VerifyTestMain(m)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Determine Source of Package Leaks
|
||||||
|
|
||||||
|
When verifying leaks using `TestMain`, the leak test is only run once after all tests
|
||||||
|
have been run. This is typically enough to ensure there's no goroutines leaked from
|
||||||
|
tests, but when there are leaks, it's hard to determine which test is causing them.
|
||||||
|
|
||||||
|
You can use the following bash script to determine the source of the failing test:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Create a test binary which will be used to run each test individually
|
||||||
|
$ go test -c -o tests
|
||||||
|
|
||||||
|
# Run each test individually, printing "." for successful tests, or the test name
|
||||||
|
# for failing tests.
|
||||||
|
$ for test in $(go test -list . | grep -E "^(Test|Example)"); do ./tests -test.run "^$test\$" &>/dev/null && echo -n "." || echo -e "\n$test failed"; done
|
||||||
|
```
|
||||||
|
|
||||||
|
This will only print names of failing tests which can be investigated individually. E.g.,
|
||||||
|
|
||||||
|
```
|
||||||
|
.....
|
||||||
|
TestLeakyTest failed
|
||||||
|
.......
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stability
|
||||||
|
|
||||||
|
goleak is v1 and follows [SemVer](http://semver.org/) strictly.
|
||||||
|
|
||||||
|
No breaking changes will be made to exported APIs before 2.0.
|
||||||
|
|
||||||
|
[doc-img]: https://godoc.org/go.uber.org/goleak?status.svg
|
||||||
|
[doc]: https://godoc.org/go.uber.org/goleak
|
||||||
|
[ci-img]: https://github.com/uber-go/goleak/actions/workflows/ci.yml/badge.svg
|
||||||
|
[ci]: https://github.com/uber-go/goleak/actions/workflows/ci.yml
|
||||||
|
[cov-img]: https://codecov.io/gh/uber-go/goleak/branch/master/graph/badge.svg
|
||||||
|
[cov]: https://codecov.io/gh/uber-go/goleak
|
||||||
|
[release]: https://go.dev/doc/devel/release#policy
|
||||||
22
vendor/go.uber.org/goleak/doc.go
generated
vendored
Normal file
22
vendor/go.uber.org/goleak/doc.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2018 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package goleak is a Goroutine leak detector.
|
||||||
|
package goleak // import "go.uber.org/goleak"
|
||||||
22
vendor/go.uber.org/goleak/internal/stack/doc.go
generated
vendored
Normal file
22
vendor/go.uber.org/goleak/internal/stack/doc.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2017-2023 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package stack is used for parsing stacks from `runtime.Stack`.
|
||||||
|
package stack
|
||||||
56
vendor/go.uber.org/goleak/internal/stack/scan.go
generated
vendored
Normal file
56
vendor/go.uber.org/goleak/internal/stack/scan.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright (c) 2023 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scanner provides a bufio.Scanner the ability to Unscan,
|
||||||
|
// which allows the current token to be read again
|
||||||
|
// after the next Scan.
|
||||||
|
type scanner struct {
|
||||||
|
*bufio.Scanner
|
||||||
|
|
||||||
|
unscanned bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newScanner(r io.Reader) *scanner {
|
||||||
|
return &scanner{Scanner: bufio.NewScanner(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *scanner) Scan() bool {
|
||||||
|
if s.unscanned {
|
||||||
|
s.unscanned = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s.Scanner.Scan()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unscan stops the scanner from advancing its position
|
||||||
|
// for the next Scan.
|
||||||
|
//
|
||||||
|
// Bytes and Text will return the same token after next Scan
|
||||||
|
// that they do right now.
|
||||||
|
func (s *scanner) Unscan() {
|
||||||
|
s.unscanned = true
|
||||||
|
}
|
||||||
298
vendor/go.uber.org/goleak/internal/stack/stacks.go
generated
vendored
Normal file
298
vendor/go.uber.org/goleak/internal/stack/stacks.go
generated
vendored
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
// Copyright (c) 2017-2023 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package stack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const _defaultBufferSize = 64 * 1024 // 64 KiB
|
||||||
|
|
||||||
|
// Stack represents a single Goroutine's stack.
|
||||||
|
type Stack struct {
|
||||||
|
id int
|
||||||
|
state string // e.g. 'running', 'chan receive'
|
||||||
|
|
||||||
|
// The first function on the stack.
|
||||||
|
firstFunction string
|
||||||
|
|
||||||
|
// A set of all functions in the stack,
|
||||||
|
allFunctions map[string]struct{}
|
||||||
|
|
||||||
|
// Full, raw stack trace.
|
||||||
|
fullStack string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the goroutine ID.
|
||||||
|
func (s Stack) ID() int {
|
||||||
|
return s.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// State returns the Goroutine's state.
|
||||||
|
func (s Stack) State() string {
|
||||||
|
return s.state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full returns the full stack trace for this goroutine.
|
||||||
|
func (s Stack) Full() string {
|
||||||
|
return s.fullStack
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstFunction returns the name of the first function on the stack.
|
||||||
|
func (s Stack) FirstFunction() string {
|
||||||
|
return s.firstFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasFunction reports whether the stack has the given function
|
||||||
|
// anywhere in it.
|
||||||
|
func (s Stack) HasFunction(name string) bool {
|
||||||
|
_, ok := s.allFunctions[name]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Stack) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Goroutine %v in state %v, with %v on top of the stack:\n%s",
|
||||||
|
s.id, s.state, s.firstFunction, s.Full())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStacks(all bool) []Stack {
|
||||||
|
trace := getStackBuffer(all)
|
||||||
|
stacks, err := newStackParser(bytes.NewReader(trace)).Parse()
|
||||||
|
if err != nil {
|
||||||
|
// Well-formed stack traces should never fail to parse.
|
||||||
|
// If they do, it's a bug in this package.
|
||||||
|
// Panic so we can fix it.
|
||||||
|
panic(fmt.Sprintf("Failed to parse stack trace: %v\n%s", err, trace))
|
||||||
|
}
|
||||||
|
return stacks
|
||||||
|
}
|
||||||
|
|
||||||
|
type stackParser struct {
|
||||||
|
scan *scanner
|
||||||
|
stacks []Stack
|
||||||
|
errors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStackParser(r io.Reader) *stackParser {
|
||||||
|
return &stackParser{
|
||||||
|
scan: newScanner(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *stackParser) Parse() ([]Stack, error) {
|
||||||
|
for p.scan.Scan() {
|
||||||
|
line := p.scan.Text()
|
||||||
|
|
||||||
|
// If we see the goroutine header, start a new stack.
|
||||||
|
if strings.HasPrefix(line, "goroutine ") {
|
||||||
|
stack, err := p.parseStack(line)
|
||||||
|
if err != nil {
|
||||||
|
p.errors = append(p.errors, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.stacks = append(p.stacks, stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.errors = append(p.errors, p.scan.Err())
|
||||||
|
return p.stacks, errors.Join(p.errors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStack parses a single stack trace from the given scanner.
|
||||||
|
// line is the first line of the stack trace, which should look like:
|
||||||
|
//
|
||||||
|
// goroutine 123 [runnable]:
|
||||||
|
func (p *stackParser) parseStack(line string) (Stack, error) {
|
||||||
|
id, state, err := parseGoStackHeader(line)
|
||||||
|
if err != nil {
|
||||||
|
return Stack{}, fmt.Errorf("parse header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the rest of the stack trace.
|
||||||
|
var (
|
||||||
|
firstFunction string
|
||||||
|
fullStack bytes.Buffer
|
||||||
|
)
|
||||||
|
funcs := make(map[string]struct{})
|
||||||
|
for p.scan.Scan() {
|
||||||
|
line := p.scan.Text()
|
||||||
|
if strings.HasPrefix(line, "goroutine ") {
|
||||||
|
// If we see the goroutine header,
|
||||||
|
// it's the end of this stack.
|
||||||
|
// Unscan so the next Scan sees the same line.
|
||||||
|
p.scan.Unscan()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fullStack.WriteString(line)
|
||||||
|
fullStack.WriteByte('\n') // scanner trims the newline
|
||||||
|
|
||||||
|
if len(line) == 0 {
|
||||||
|
// Empty line usually marks the end of the stack
|
||||||
|
// but we don't want to have to rely on that.
|
||||||
|
// Just skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
funcName, creator, err := parseFuncName(line)
|
||||||
|
if err != nil {
|
||||||
|
return Stack{}, fmt.Errorf("parse function: %w", err)
|
||||||
|
}
|
||||||
|
if !creator {
|
||||||
|
// A function is part of a goroutine's stack
|
||||||
|
// only if it's not a "created by" function.
|
||||||
|
//
|
||||||
|
// The creator function is part of a different stack.
|
||||||
|
// We don't care about it right now.
|
||||||
|
funcs[funcName] = struct{}{}
|
||||||
|
if firstFunction == "" {
|
||||||
|
firstFunction = funcName
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// The function name followed by a line in the form:
|
||||||
|
//
|
||||||
|
// <tab>example.com/path/to/package/file.go:123 +0x123
|
||||||
|
//
|
||||||
|
// We don't care about the position so we can skip this line.
|
||||||
|
if p.scan.Scan() {
|
||||||
|
// Be defensive:
|
||||||
|
// Skip the line only if it starts with a tab.
|
||||||
|
bs := p.scan.Bytes()
|
||||||
|
if len(bs) > 0 && bs[0] == '\t' {
|
||||||
|
fullStack.Write(bs)
|
||||||
|
fullStack.WriteByte('\n')
|
||||||
|
} else {
|
||||||
|
// Put it back and let the next iteration handle it
|
||||||
|
// if it doesn't start with a tab.
|
||||||
|
p.scan.Unscan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if creator {
|
||||||
|
// The "created by" line is the last line of the stack.
|
||||||
|
// We can stop parsing now.
|
||||||
|
//
|
||||||
|
// Note that if tracebackancestors=N is set,
|
||||||
|
// there may be more a traceback of the creator function
|
||||||
|
// following the "created by" line,
|
||||||
|
// but it should not be considered part of this stack.
|
||||||
|
// e.g.,
|
||||||
|
//
|
||||||
|
// created by testing.(*T).Run in goroutine 1
|
||||||
|
// /usr/lib/go/src/testing/testing.go:1648 +0x3ad
|
||||||
|
// [originating from goroutine 1]:
|
||||||
|
// testing.(*T).Run(...)
|
||||||
|
// /usr/lib/go/src/testing/testing.go:1649 +0x3ad
|
||||||
|
//
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stack{
|
||||||
|
id: id,
|
||||||
|
state: state,
|
||||||
|
firstFunction: firstFunction,
|
||||||
|
allFunctions: funcs,
|
||||||
|
fullStack: fullStack.String(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// All returns the stacks for all running goroutines.
|
||||||
|
func All() []Stack {
|
||||||
|
return getStacks(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns the stack for the current goroutine.
|
||||||
|
func Current() Stack {
|
||||||
|
return getStacks(false)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStackBuffer(all bool) []byte {
|
||||||
|
for i := _defaultBufferSize; ; i *= 2 {
|
||||||
|
buf := make([]byte, i)
|
||||||
|
if n := runtime.Stack(buf, all); n < i {
|
||||||
|
return buf[:n]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a single function from the given line.
|
||||||
|
// The line is in one of these formats:
|
||||||
|
//
|
||||||
|
// example.com/path/to/package.funcName(args...)
|
||||||
|
// example.com/path/to/package.(*typeName).funcName(args...)
|
||||||
|
// created by example.com/path/to/package.funcName
|
||||||
|
// created by example.com/path/to/package.funcName in goroutine [...]
|
||||||
|
//
|
||||||
|
// Also reports whether the line was a "created by" line.
|
||||||
|
func parseFuncName(line string) (name string, creator bool, err error) {
|
||||||
|
if after, ok := strings.CutPrefix(line, "created by "); ok {
|
||||||
|
// The function name is the part after "created by "
|
||||||
|
// and before " in goroutine [...]".
|
||||||
|
idx := strings.Index(after, " in goroutine")
|
||||||
|
if idx >= 0 {
|
||||||
|
after = after[:idx]
|
||||||
|
}
|
||||||
|
name = after
|
||||||
|
creator = true
|
||||||
|
} else if idx := strings.LastIndexByte(line, '('); idx >= 0 {
|
||||||
|
// The function name is the part before the last '('.
|
||||||
|
name = line[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return "", false, fmt.Errorf("no function found: %q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, creator, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGoStackHeader parses a stack header that looks like:
|
||||||
|
// goroutine 643 [runnable]:\n
|
||||||
|
// And returns the goroutine ID, and the state.
|
||||||
|
func parseGoStackHeader(line string) (goroutineID int, state string, err error) {
|
||||||
|
// The scanner will have already trimmed the "\n",
|
||||||
|
// but we'll guard against it just in case.
|
||||||
|
//
|
||||||
|
// Trimming them separately makes them both optional.
|
||||||
|
line = strings.TrimSuffix(strings.TrimSuffix(line, ":"), "\n")
|
||||||
|
parts := strings.SplitN(line, " ", 3)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return 0, "", fmt.Errorf("unexpected format: %q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("bad goroutine ID %q in line %q", parts[1], line)
|
||||||
|
}
|
||||||
|
|
||||||
|
state = strings.TrimSuffix(strings.TrimPrefix(parts[2], "["), "]")
|
||||||
|
return id, state, nil
|
||||||
|
}
|
||||||
108
vendor/go.uber.org/goleak/leaks.go
generated
vendored
Normal file
108
vendor/go.uber.org/goleak/leaks.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package goleak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"go.uber.org/goleak/internal/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestingT is the minimal subset of testing.TB that we use.
|
||||||
|
type TestingT interface {
|
||||||
|
Error(...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// filterStacks will filter any stacks excluded by the given opts.
|
||||||
|
// filterStacks modifies the passed in stacks slice.
|
||||||
|
func filterStacks(stacks []stack.Stack, skipID int, opts *opts) []stack.Stack {
|
||||||
|
filtered := stacks[:0]
|
||||||
|
for _, stack := range stacks {
|
||||||
|
// Always skip the running goroutine.
|
||||||
|
if stack.ID() == skipID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Run any default or user-specified filters.
|
||||||
|
if opts.filter(stack) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filtered = append(filtered, stack)
|
||||||
|
}
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find looks for extra goroutines, and returns a descriptive error if
|
||||||
|
// any are found.
|
||||||
|
func Find(options ...Option) error {
|
||||||
|
cur := stack.Current().ID()
|
||||||
|
|
||||||
|
opts := buildOpts(options...)
|
||||||
|
if opts.cleanup != nil {
|
||||||
|
return errors.New("Cleanup can only be passed to VerifyNone or VerifyTestMain")
|
||||||
|
}
|
||||||
|
var stacks []stack.Stack
|
||||||
|
retry := true
|
||||||
|
for i := 0; retry; i++ {
|
||||||
|
stacks = filterStacks(stack.All(), cur, opts)
|
||||||
|
|
||||||
|
if len(stacks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
retry = opts.retry(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("found unexpected goroutines:\n%s", stacks)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testHelper interface {
|
||||||
|
Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyNone marks the given TestingT as failed if any extra goroutines are
|
||||||
|
// found by Find. This is a helper method to make it easier to integrate in
|
||||||
|
// tests by doing:
|
||||||
|
//
|
||||||
|
// defer VerifyNone(t)
|
||||||
|
//
|
||||||
|
// VerifyNone is currently incompatible with t.Parallel because it cannot
|
||||||
|
// associate specific goroutines with specific tests. Thus, non-leaking
|
||||||
|
// goroutines from other tests running in parallel could fail this check.
|
||||||
|
// If you need to run tests in parallel, use [VerifyTestMain] instead,
|
||||||
|
// which will verify that no leaking goroutines exist after ALL tests finish.
|
||||||
|
func VerifyNone(t TestingT, options ...Option) {
|
||||||
|
opts := buildOpts(options...)
|
||||||
|
var cleanup func(int)
|
||||||
|
cleanup, opts.cleanup = opts.cleanup, nil
|
||||||
|
|
||||||
|
if h, ok := t.(testHelper); ok {
|
||||||
|
// Mark this function as a test helper, if available.
|
||||||
|
h.Helper()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Find(opts); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cleanup != nil {
|
||||||
|
cleanup(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
198
vendor/go.uber.org/goleak/options.go
generated
vendored
Normal file
198
vendor/go.uber.org/goleak/options.go
generated
vendored
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// Copyright (c) 2017-2023 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package goleak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/goleak/internal/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option lets users specify custom verifications.
|
||||||
|
type Option interface {
|
||||||
|
apply(*opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We retry up to 20 times if we can't find the goroutine that
|
||||||
|
// we are looking for. In between each attempt, we will sleep for
|
||||||
|
// a short while to let any running goroutines complete.
|
||||||
|
const _defaultRetries = 20
|
||||||
|
|
||||||
|
type opts struct {
|
||||||
|
filters []func(stack.Stack) bool
|
||||||
|
maxRetries int
|
||||||
|
maxSleep time.Duration
|
||||||
|
cleanup func(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement apply so that opts struct itself can be used as
|
||||||
|
// an Option.
|
||||||
|
func (o *opts) apply(opts *opts) {
|
||||||
|
opts.filters = o.filters
|
||||||
|
opts.maxRetries = o.maxRetries
|
||||||
|
opts.maxSleep = o.maxSleep
|
||||||
|
opts.cleanup = o.cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
// optionFunc lets us easily write options without a custom type.
|
||||||
|
type optionFunc func(*opts)
|
||||||
|
|
||||||
|
func (f optionFunc) apply(opts *opts) { f(opts) }
|
||||||
|
|
||||||
|
// IgnoreTopFunction ignores any goroutines where the specified function
|
||||||
|
// is at the top of the stack. The function name should be fully qualified,
|
||||||
|
// e.g., go.uber.org/goleak.IgnoreTopFunction
|
||||||
|
func IgnoreTopFunction(f string) Option {
|
||||||
|
return addFilter(func(s stack.Stack) bool {
|
||||||
|
return s.FirstFunction() == f
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreAnyFunction ignores goroutines where the specified function
|
||||||
|
// is present anywhere in the stack.
|
||||||
|
//
|
||||||
|
// The function name must be fully qualified, e.g.,
|
||||||
|
//
|
||||||
|
// go.uber.org/goleak.IgnoreAnyFunction
|
||||||
|
//
|
||||||
|
// For methods, the fully qualified form looks like:
|
||||||
|
//
|
||||||
|
// go.uber.org/goleak.(*MyType).MyMethod
|
||||||
|
func IgnoreAnyFunction(f string) Option {
|
||||||
|
return addFilter(func(s stack.Stack) bool {
|
||||||
|
return s.HasFunction(f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup sets up a cleanup function that will be executed at the
|
||||||
|
// end of the leak check.
|
||||||
|
// When passed to [VerifyTestMain], the exit code passed to cleanupFunc
|
||||||
|
// will be set to the exit code of TestMain.
|
||||||
|
// When passed to [VerifyNone], the exit code will be set to 0.
|
||||||
|
// This cannot be passed to [Find].
|
||||||
|
func Cleanup(cleanupFunc func(exitCode int)) Option {
|
||||||
|
return optionFunc(func(opts *opts) {
|
||||||
|
opts.cleanup = cleanupFunc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreCurrent records all current goroutines when the option is created, and ignores
|
||||||
|
// them in any future Find/Verify calls.
|
||||||
|
func IgnoreCurrent() Option {
|
||||||
|
excludeIDSet := map[int]bool{}
|
||||||
|
for _, s := range stack.All() {
|
||||||
|
excludeIDSet[s.ID()] = true
|
||||||
|
}
|
||||||
|
return addFilter(func(s stack.Stack) bool {
|
||||||
|
return excludeIDSet[s.ID()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxSleep(d time.Duration) Option {
|
||||||
|
return optionFunc(func(opts *opts) {
|
||||||
|
opts.maxSleep = d
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFilter(f func(stack.Stack) bool) Option {
|
||||||
|
return optionFunc(func(opts *opts) {
|
||||||
|
opts.filters = append(opts.filters, f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOpts(options ...Option) *opts {
|
||||||
|
opts := &opts{
|
||||||
|
maxRetries: _defaultRetries,
|
||||||
|
maxSleep: 100 * time.Millisecond,
|
||||||
|
}
|
||||||
|
opts.filters = append(opts.filters,
|
||||||
|
isTestStack,
|
||||||
|
isSyscallStack,
|
||||||
|
isStdLibStack,
|
||||||
|
isTraceStack,
|
||||||
|
)
|
||||||
|
for _, option := range options {
|
||||||
|
option.apply(opts)
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opts) filter(s stack.Stack) bool {
|
||||||
|
for _, filter := range o.filters {
|
||||||
|
if filter(s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opts) retry(i int) bool {
|
||||||
|
if i >= o.maxRetries {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
d := time.Duration(int(time.Microsecond) << uint(i))
|
||||||
|
if d > o.maxSleep {
|
||||||
|
d = o.maxSleep
|
||||||
|
}
|
||||||
|
time.Sleep(d)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTestStack is a default filter installed to automatically skip goroutines
|
||||||
|
// that the testing package runs while the user's tests are running.
|
||||||
|
func isTestStack(s stack.Stack) bool {
|
||||||
|
// Until go1.7, the main goroutine ran RunTests, which started
|
||||||
|
// the test in a separate goroutine and waited for that test goroutine
|
||||||
|
// to end by waiting on a channel.
|
||||||
|
// Since go1.7, a separate goroutine is started to wait for signals.
|
||||||
|
// T.Parallel is for parallel tests, which are blocked until all serial
|
||||||
|
// tests have run with T.Parallel at the top of the stack.
|
||||||
|
// testing.runFuzzTests is for fuzz testing, it's blocked until the test
|
||||||
|
// function with all seed corpus have run.
|
||||||
|
// testing.runFuzzing is for fuzz testing, it's blocked until a failing
|
||||||
|
// input is found.
|
||||||
|
switch s.FirstFunction() {
|
||||||
|
case "testing.RunTests", "testing.(*T).Run", "testing.(*T).Parallel", "testing.runFuzzing", "testing.runFuzzTests":
|
||||||
|
// In pre1.7 and post-1.7, background goroutines started by the testing
|
||||||
|
// package are blocked waiting on a channel.
|
||||||
|
return strings.HasPrefix(s.State(), "chan receive")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSyscallStack(s stack.Stack) bool {
|
||||||
|
// Typically runs in the background when code uses CGo:
|
||||||
|
// https://github.com/golang/go/issues/16714
|
||||||
|
return s.HasFunction("runtime.goexit") && strings.HasPrefix(s.State(), "syscall")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStdLibStack(s stack.Stack) bool {
|
||||||
|
// Importing os/signal starts a background goroutine.
|
||||||
|
// The name of the function at the top has changed between versions.
|
||||||
|
if f := s.FirstFunction(); f == "os/signal.signal_recv" || f == "os/signal.loop" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using signal.Notify will start a runtime goroutine.
|
||||||
|
return s.HasFunction("runtime.ensureSigM")
|
||||||
|
}
|
||||||
69
vendor/go.uber.org/goleak/testmain.go
generated
vendored
Normal file
69
vendor/go.uber.org/goleak/testmain.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package goleak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Variables for stubbing in unit tests.
|
||||||
|
var (
|
||||||
|
_osExit = os.Exit
|
||||||
|
_osStderr io.Writer = os.Stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestingM is the minimal subset of testing.M that we use.
|
||||||
|
type TestingM interface {
|
||||||
|
Run() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyTestMain can be used in a TestMain function for package tests to
|
||||||
|
// verify that there were no goroutine leaks.
|
||||||
|
// To use it, your TestMain function should look like:
|
||||||
|
//
|
||||||
|
// func TestMain(m *testing.M) {
|
||||||
|
// goleak.VerifyTestMain(m)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// See https://golang.org/pkg/testing/#hdr-Main for more details.
|
||||||
|
//
|
||||||
|
// This will run all tests as per normal, and if they were successful, look
|
||||||
|
// for any goroutine leaks and fail the tests if any leaks were found.
|
||||||
|
func VerifyTestMain(m TestingM, options ...Option) {
|
||||||
|
exitCode := m.Run()
|
||||||
|
opts := buildOpts(options...)
|
||||||
|
|
||||||
|
var cleanup func(int)
|
||||||
|
cleanup, opts.cleanup = opts.cleanup, nil
|
||||||
|
if cleanup == nil {
|
||||||
|
cleanup = _osExit
|
||||||
|
}
|
||||||
|
defer func() { cleanup(exitCode) }()
|
||||||
|
|
||||||
|
if exitCode == 0 {
|
||||||
|
if err := Find(opts); err != nil {
|
||||||
|
fmt.Fprintf(_osStderr, "goleak: Errors on successful test run: %v\n", err)
|
||||||
|
exitCode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
vendor/go.uber.org/goleak/tracestack_new.go
generated
vendored
Normal file
30
vendor/go.uber.org/goleak/tracestack_new.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2021-2023 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
//go:build go1.16
|
||||||
|
// +build go1.16
|
||||||
|
|
||||||
|
package goleak
|
||||||
|
|
||||||
|
import "go.uber.org/goleak/internal/stack"
|
||||||
|
|
||||||
|
func isTraceStack(s stack.Stack) bool {
|
||||||
|
return s.HasFunction("runtime.ReadTrace")
|
||||||
|
}
|
||||||
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -370,6 +370,10 @@ go.uber.org/automaxprocs
|
|||||||
go.uber.org/automaxprocs/internal/cgroups
|
go.uber.org/automaxprocs/internal/cgroups
|
||||||
go.uber.org/automaxprocs/internal/runtime
|
go.uber.org/automaxprocs/internal/runtime
|
||||||
go.uber.org/automaxprocs/maxprocs
|
go.uber.org/automaxprocs/maxprocs
|
||||||
|
# go.uber.org/goleak v1.3.0
|
||||||
|
## explicit; go 1.20
|
||||||
|
go.uber.org/goleak
|
||||||
|
go.uber.org/goleak/internal/stack
|
||||||
# go.uber.org/multierr v1.11.0
|
# go.uber.org/multierr v1.11.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
go.uber.org/multierr
|
go.uber.org/multierr
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user