Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support tracebackancestors? #70

Open
MichaelSnowden opened this issue Jan 4, 2022 · 2 comments
Open

Support tracebackancestors? #70

MichaelSnowden opened this issue Jan 4, 2022 · 2 comments

Comments

@MichaelSnowden
Copy link

I'd like to verify that there are no goroutines leaked in my test, but only for goroutines where my test itself is an ancestor goroutine. AFAIK, this is not what goleak currently does. It looks at all goroutines, which means it doesn't work in parallel, and it doesn't work for many globally initialized goroutines like those created in the init function of some imported package.

There is a tracebackancestors option that can be passed to the GODEBUG environment variable (documented here, withe the original accepted proposal here. This option adds ancestry metadata to goroutines, which should make the functionality I'm looking for possible.

Are there any plans to add this functionality to goleak?

@prashantv
Copy link
Collaborator

I'm all for investing in a solution that can work with tests being run in parallel!

To understand how tracebackancestors works, I created a small example:
https://go.dev/play/p/XBvJ9nS4Zgy

In this case, all goroutines are rooted in the "main" goroutine, but there's 2 paths:

  • main --> foo (synchronous) --> bar (goroutine) --> baz (goroutine)
  • main --> bar (synchronous) --> baz (goroutine)

Imagine main is the test goroutine. In this case, we want to try and trace the running baz goroutines back to main. If we run this with ancestry enabled, we get the following stacktrace:

goroutine 1 [running]:
main.main()
        /home/prashant/tmp/goancestry.go:18 +0x5c

goroutine 7 [select (no cases)]:
main.baz()
        /home/prashant/tmp/goancestry.go:31 +0x17
created by main.bar
        /home/prashant/tmp/goancestry.go:27 +0x25
[originating from goroutine 1]:
main.bar(...)
        /home/prashant/tmp/goancestry.go:28 +0x25
main.main(...)
        /home/prashant/tmp/goancestry.go:15 +0x25

goroutine 8 [select (no cases)]:
main.baz()
        /home/prashant/tmp/goancestry.go:31 +0x17
created by main.bar
        /home/prashant/tmp/goancestry.go:27 +0x25
[originating from goroutine 6]:
main.bar(...)
        /home/prashant/tmp/goancestry.go:28 +0x25
created by main.foo
        /home/prashant/tmp/goancestry.go:23 +0x25

We can trace the main --> bar --> baz back, but we can't trace main --> foo --> bar --> baz back, since the goroutine running foo has exited. So we see "originating from goroutine 6", but we don't know what goroutine 6 is, or where it originated from.

It looks like even with support for traceback ancestors, it wouldn't be possible to track leaks back to tests reliably -- it would have false negatives, missing leaks if the test starts G1, and G1 starts G2 which leaks, but G1 is no longer running.

I wasn't aware of this GODEBUG option previously, so may be missing something.

@edaniels
Copy link

edaniels commented Oct 3, 2024

@prashantv even though goroutine 6 is gone in your example, in test and production cases, it's still been very helpful for me and teammates to know at least what goroutine 6 looked like in order to trace the origin of a leak. When I was originally working on the ancestry feature, it was to solve an annoying leak where we were making connections that were leaking but we couldn't discern if the connections were started by a library we ran for customers or a library that we run for internal communications since they were all stuck in tls readLoops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants