From 48567c0015d9a4010caaa769d1b95022778102bf Mon Sep 17 00:00:00 2001 From: Nedyalko Andreev Date: Tue, 6 Dec 2022 00:56:38 +0200 Subject: [PATCH 1/2] Ensure the REST API server is properly stopped before `k6 run` finishes --- api/server.go | 10 +++++----- cmd/run.go | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/api/server.go b/api/server.go index dabfea0567de..caed31b692f2 100644 --- a/api/server.go +++ b/api/server.go @@ -3,6 +3,7 @@ package api import ( "fmt" "net/http" + "time" "github.com/sirupsen/logrus" @@ -19,11 +20,10 @@ func newHandler(logger logrus.FieldLogger) http.Handler { return mux } -// ListenAndServe is analogous to the stdlib one but also takes a core.Engine and logrus.FieldLogger -func ListenAndServe(addr string, engine *core.Engine, logger logrus.FieldLogger) error { - mux := newHandler(logger) - - return http.ListenAndServe(addr, withEngine(engine, newLogger(logger, mux))) +// GetServer returns a http.Server instance that can serve k6's REST API. +func GetServer(addr string, engine *core.Engine, logger logrus.FieldLogger) *http.Server { + mux := withEngine(engine, newLogger(logger, newHandler(logger))) + return &http.Server{Addr: addr, Handler: mux, ReadHeaderTimeout: 10 * time.Second} } type wrappedResponseWriter struct { diff --git a/cmd/run.go b/cmd/run.go index 5e2f55a24780..ab9c5f80d642 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -117,10 +117,20 @@ func (c *cmdRun) run(cmd *cobra.Command, args []string) error { // Spin up the REST API server, if not disabled. if c.gs.flags.address != "" { initBar.Modify(pb.WithConstProgress(0, "Init API server")) + + apiWG := &sync.WaitGroup{} + apiWG.Add(2) + defer apiWG.Wait() + + srvCtx, srvCancel := context.WithCancel(globalCtx) + defer srvCancel() + + // TODO: send the ExecutionState and MetricsEngine instead of the Engine + srv := api.GetServer(c.gs.flags.address, engine, logger) go func() { + defer apiWG.Done() logger.Debugf("Starting the REST API server on %s", c.gs.flags.address) - // TODO: send the ExecutionState and MetricsEngine instead of the Engine - if aerr := api.ListenAndServe(c.gs.flags.address, engine, logger); aerr != nil { + if aerr := srv.ListenAndServe(); aerr != nil && !errors.Is(aerr, http.ErrServerClosed) { // Only exit k6 if the user has explicitly set the REST API address if cmd.Flags().Lookup("address").Changed { logger.WithError(aerr).Error("Error from API server") @@ -130,6 +140,13 @@ func (c *cmdRun) run(cmd *cobra.Command, args []string) error { } } }() + go func() { + defer apiWG.Done() + <-srvCtx.Done() + if aerr := srv.Close(); aerr != nil { + logger.WithError(aerr).Debug("REST API server did not shut down correctly") + } + }() } // We do this here so we can get any output URLs below. From 2cb2c74022a8bb92a0f6a85c02da7bcc81429b5e Mon Sep 17 00:00:00 2001 From: Nedyalko Andreev Date: Tue, 6 Dec 2022 00:57:53 +0200 Subject: [PATCH 2/2] Check for goroutine leaks with go.uber.org/goleak --- cmd/root_test.go | 11 +- go.mod | 4 +- go.sum | 5 + vendor/go.uber.org/goleak/.gitignore | 5 + vendor/go.uber.org/goleak/CHANGELOG.md | 51 +++++ vendor/go.uber.org/goleak/LICENSE | 21 +++ vendor/go.uber.org/goleak/README.md | 71 +++++++ vendor/go.uber.org/goleak/doc.go | 22 +++ .../goleak/internal/stack/stacks.go | 155 +++++++++++++++ vendor/go.uber.org/goleak/leaks.go | 102 ++++++++++ vendor/go.uber.org/goleak/options.go | 178 ++++++++++++++++++ vendor/go.uber.org/goleak/testmain.go | 69 +++++++ vendor/go.uber.org/goleak/tracestack_new.go | 34 ++++ vendor/modules.txt | 6 + 14 files changed, 732 insertions(+), 2 deletions(-) create mode 100644 vendor/go.uber.org/goleak/.gitignore create mode 100644 vendor/go.uber.org/goleak/CHANGELOG.md create mode 100644 vendor/go.uber.org/goleak/LICENSE create mode 100644 vendor/go.uber.org/goleak/README.md create mode 100644 vendor/go.uber.org/goleak/doc.go create mode 100644 vendor/go.uber.org/goleak/internal/stack/stacks.go create mode 100644 vendor/go.uber.org/goleak/leaks.go create mode 100644 vendor/go.uber.org/goleak/options.go create mode 100644 vendor/go.uber.org/goleak/testmain.go create mode 100644 vendor/go.uber.org/goleak/tracestack_new.go diff --git a/cmd/root_test.go b/cmd/root_test.go index 1ff1b6fa00e7..452ad2471329 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.k6.io/k6/lib/testutils" + "go.uber.org/goleak" ) type blockingTransport struct { @@ -59,7 +60,15 @@ func TestMain(m *testing.M) { } }() - // TODO: add https://github.com/uber-go/goleak + defer func() { + // TODO: figure out why logrus' `Entry.WriterLevel` goroutine sticks + // around and remove this exception. + opt := goleak.IgnoreTopFunction("io.(*pipe).read") + if err := goleak.Find(opt); err != nil { + fmt.Println(err) //nolint:forbidigo + exitCode = 3 + } + }() exitCode = m.Run() } diff --git a/go.mod b/go.mod index d51c77152339..7b3598979a7d 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/andybalholm/brotli v1.0.4 github.com/dop251/goja v0.0.0-20221106173738-3b8a68ca89b4 github.com/fatih/color v1.13.0 + github.com/go-sourcemap/sourcemap v2.1.4-0.20211119122758-180fcef48034+incompatible github.com/golang/protobuf v1.5.2 github.com/gorilla/websocket v1.5.0 github.com/grafana/xk6-output-prometheus-remote v0.0.8 @@ -36,6 +37,7 @@ require ( github.com/stretchr/testify v1.8.0 github.com/tidwall/gjson v1.14.3 github.com/tidwall/pretty v1.2.1 + go.uber.org/goleak v1.2.0 golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be golang.org/x/net v0.0.0-20221002022538-bcab6841153b golang.org/x/term v0.0.0-20220919170432-7a66f970e087 @@ -55,7 +57,6 @@ require ( github.com/dlclark/regexp2 v1.7.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/go-sourcemap/sourcemap v2.1.4-0.20211119122758-180fcef48034+incompatible github.com/golang/snappy v0.0.4 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -68,6 +69,7 @@ require ( github.com/tidwall/match v1.1.1 // indirect go.buf.build/grpc/go/gogo/protobuf v1.4.9 // indirect go.buf.build/grpc/go/prometheus/prometheus v1.4.4 // indirect + golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect diff --git a/go.sum b/go.sum index 192d20b28970..5a8ab2d572e3 100644 --- a/go.sum +++ b/go.sum @@ -371,6 +371,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -402,6 +404,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -591,6 +594,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/go.uber.org/goleak/.gitignore b/vendor/go.uber.org/goleak/.gitignore new file mode 100644 index 000000000000..0fff519a4ab7 --- /dev/null +++ b/vendor/go.uber.org/goleak/.gitignore @@ -0,0 +1,5 @@ +vendor/ +/bin +/lint.log +/cover.out +/cover.html diff --git a/vendor/go.uber.org/goleak/CHANGELOG.md b/vendor/go.uber.org/goleak/CHANGELOG.md new file mode 100644 index 000000000000..761db2caa555 --- /dev/null +++ b/vendor/go.uber.org/goleak/CHANGELOG.md @@ -0,0 +1,51 @@ +# 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.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. diff --git a/vendor/go.uber.org/goleak/LICENSE b/vendor/go.uber.org/goleak/LICENSE new file mode 100644 index 000000000000..6c9bde216e21 --- /dev/null +++ b/vendor/go.uber.org/goleak/LICENSE @@ -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. diff --git a/vendor/go.uber.org/goleak/README.md b/vendor/go.uber.org/goleak/README.md new file mode 100644 index 000000000000..fb92dabc56d8 --- /dev/null +++ b/vendor/go.uber.org/goleak/README.md @@ -0,0 +1,71 @@ +# 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. It is compatible with Go 1.5+. + +## 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/go.yml/badge.svg +[ci]: https://github.com/uber-go/goleak/actions/workflows/go.yml +[cov-img]: https://codecov.io/gh/uber-go/goleak/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/goleak diff --git a/vendor/go.uber.org/goleak/doc.go b/vendor/go.uber.org/goleak/doc.go new file mode 100644 index 000000000000..3832f8dbc5f4 --- /dev/null +++ b/vendor/go.uber.org/goleak/doc.go @@ -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" diff --git a/vendor/go.uber.org/goleak/internal/stack/stacks.go b/vendor/go.uber.org/goleak/internal/stack/stacks.go new file mode 100644 index 000000000000..94f82e4c0d50 --- /dev/null +++ b/vendor/go.uber.org/goleak/internal/stack/stacks.go @@ -0,0 +1,155 @@ +// 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 stack + +import ( + "bufio" + "bytes" + "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 + firstFunction string + fullStack *bytes.Buffer +} + +// 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.String() +} + +// FirstFunction returns the name of the first function on the stack. +func (s Stack) FirstFunction() string { + return s.firstFunction +} + +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 { + var stacks []Stack + + var curStack *Stack + stackReader := bufio.NewReader(bytes.NewReader(getStackBuffer(all))) + for { + line, err := stackReader.ReadString('\n') + if err == io.EOF { + break + } + if err != nil { + // We're reading using bytes.NewReader which should never fail. + panic("bufio.NewReader failed on a fixed string") + } + + // If we see the goroutine header, start a new stack. + isFirstLine := false + if strings.HasPrefix(line, "goroutine ") { + // flush any previous stack + if curStack != nil { + stacks = append(stacks, *curStack) + } + id, goState := parseGoStackHeader(line) + curStack = &Stack{ + id: id, + state: goState, + fullStack: &bytes.Buffer{}, + } + isFirstLine = true + } + curStack.fullStack.WriteString(line) + if !isFirstLine && curStack.firstFunction == "" { + curStack.firstFunction = parseFirstFunc(line) + } + } + + if curStack != nil { + stacks = append(stacks, *curStack) + } + return stacks +} + +// 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] + } + } +} + +func parseFirstFunc(line string) string { + line = strings.TrimSpace(line) + if idx := strings.LastIndex(line, "("); idx > 0 { + return line[:idx] + } + panic(fmt.Sprintf("function calls missing parents: %q", line)) +} + +// 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) { + line = strings.TrimSuffix(line, ":\n") + parts := strings.SplitN(line, " ", 3) + if len(parts) != 3 { + panic(fmt.Sprintf("unexpected stack header format: %q", line)) + } + + id, err := strconv.Atoi(parts[1]) + if err != nil { + panic(fmt.Sprintf("failed to parse goroutine ID: %v in line %q", parts[1], line)) + } + + state = strings.TrimSuffix(strings.TrimPrefix(parts[2], "["), "]") + return id, state +} diff --git a/vendor/go.uber.org/goleak/leaks.go b/vendor/go.uber.org/goleak/leaks.go new file mode 100644 index 000000000000..ee122b7464eb --- /dev/null +++ b/vendor/go.uber.org/goleak/leaks.go @@ -0,0 +1,102 @@ +// 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) +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) + } +} diff --git a/vendor/go.uber.org/goleak/options.go b/vendor/go.uber.org/goleak/options.go new file mode 100644 index 000000000000..d2d473b71eb6 --- /dev/null +++ b/vendor/go.uber.org/goleak/options.go @@ -0,0 +1,178 @@ +// 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 ( + "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 + }) +} + +// 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. + switch s.FirstFunction() { + case "testing.RunTests", "testing.(*T).Run", "testing.(*T).Parallel": + // 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.FirstFunction() == "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 strings.Contains(s.Full(), "runtime.ensureSigM") +} diff --git a/vendor/go.uber.org/goleak/testmain.go b/vendor/go.uber.org/goleak/testmain.go new file mode 100644 index 000000000000..7b1a50b7afba --- /dev/null +++ b/vendor/go.uber.org/goleak/testmain.go @@ -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 + } + } +} diff --git a/vendor/go.uber.org/goleak/tracestack_new.go b/vendor/go.uber.org/goleak/tracestack_new.go new file mode 100644 index 000000000000..d12ffd840435 --- /dev/null +++ b/vendor/go.uber.org/goleak/tracestack_new.go @@ -0,0 +1,34 @@ +// Copyright (c) 2021 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 ( + "strings" + + "go.uber.org/goleak/internal/stack" +) + +func isTraceStack(s stack.Stack) bool { + return strings.Contains(s.Full(), "runtime.ReadTrace") +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c63d42fbc4dc..ee4f3b04f1fc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -218,11 +218,17 @@ go.buf.build/grpc/go/gogo/protobuf/gogoproto # go.buf.build/grpc/go/prometheus/prometheus v1.4.4 ## explicit go.buf.build/grpc/go/prometheus/prometheus +# go.uber.org/goleak v1.2.0 +## explicit; go 1.18 +go.uber.org/goleak +go.uber.org/goleak/internal/stack # golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be ## explicit; go 1.17 golang.org/x/crypto/md4 golang.org/x/crypto/ocsp golang.org/x/crypto/ripemd160 +# golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 +## explicit; go 1.11 # golang.org/x/net v0.0.0-20221002022538-bcab6841153b ## explicit; go 1.17 golang.org/x/net/html