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

goleak finds goroutine leak in not enabled test case #83

Open
szuecs opened this issue Nov 11, 2022 · 4 comments
Open

goleak finds goroutine leak in not enabled test case #83

szuecs opened this issue Nov 11, 2022 · 4 comments
Assignees
Labels

Comments

@szuecs
Copy link

szuecs commented Nov 11, 2022

If you have a module with a leak in func leak():

package foo

import (
	"sync"
	"time"
)

type O struct {
	once *sync.Once
	quit chan struct{}
}

func NewO() *O {
	o := &O{
		once: &sync.Once{},
		quit: make(chan struct{}),
	}
	return o
}

func (o *O) leak() {
	go func() {
		d := 100 * time.Millisecond
		for {
			select {
			case <-o.quit:
				return
			case <-time.After(2 * d):
				time.Sleep(d)
			}

		}

	}()
}

func (o *O) close() {
	o.once.Do(func() {
		close(o.quit)
	})
}

func (*O) run(d time.Duration) {
	time.Sleep(d)
}

and tests checking for leaks by running defer goleak.VerifyNone(t), that don't run leak(), it will show a leak in TestC, even if the leak is in TestB without defer goleak.VerifyNone(t):

package foo

import (
	"testing"
	"time"

	"go.uber.org/goleak"
)

func TestA(t *testing.T) {
	defer goleak.VerifyNone(t)

	o := NewO()
	defer o.close()
	o.run(time.Second)
}

func TestB(t *testing.T) {
	o := NewO()
	o.leak()
	o.run(time.Second)
}

func TestC(t *testing.T) {
	defer goleak.VerifyNone(t)

	o := NewO()
	defer o.close()
	o.run(time.Second)
}

It depends on order of execution of tests.

Here a test run:

% go test .
--- FAIL: TestC (1.45s)
    foo_test.go:30: found unexpected goroutines:
        [Goroutine 6 in state select, with foo.(*O).leak.func1 on top of the stack:
        goroutine 6 [select]:
        foo.(*O).leak.func1()
                /tmp/go/goleak/foo.go:25 +0x85
        created by foo.(*O).leak
                /tmp/go/goleak/foo.go:22 +0x56
        ]
FAIL
FAIL    foo     3.466s
FAIL
@JacobOaks
Copy link
Contributor

JacobOaks commented Mar 28, 2023

Hey,

So I've been looking into this a bit, and at its core, this issue is very similar to supporting t.Parallel (#16). We basically want to be able to filter goroutines to only the ones whose ancestry can be linked to the specific test that VerifyNone is being called from.

This is a bit of a non-trivial problem. One idea is to use tracebackancestors, one of the runtime GODEBUG variables, but as pointed out in #70, this doesn't soundly catch all cases because goroutines whose parents are killed won't be able to be traced back to their original test function.

Another idea is to keep some sort of internal ignore list for goroutines to filter by that gets augmented at the end of every test. This is the equivalent of calling IgnoreCurrent after every test function and building up a large ignore list, but doing it internally. But this (a) won't work with parallel and (b) will require making some API call at the end of every test function, so that's not very satisfying either.

To solve this issue, we'd want some sort of way to attach baggage to created goroutines so that we can make them aware of the test function they originated from, but unfortunately this is not something Go supports.

@Liuhaai
Copy link

Liuhaai commented Aug 5, 2023

same bug happens to me. The goleak lib needs to be removed in the impacted tests to make CI work

1 similar comment
@appleezhang
Copy link

same bug happens to me. The goleak lib needs to be removed in the impacted tests to make CI work

@abhinav
Copy link
Collaborator

abhinav commented Oct 10, 2023

Just echoing what @JacobOaks said: this isn't easily solvable. There was some prior discussion here: #16 (comment)

Basically, there's no easy way to associate goroutines to the test that spawn it when used with t.Parallel.
The recommended workaround is to use the VerifyTestMain variant of detecting leaks.

@JacobOaks medium term, we should probably document this in VerifyNone docs: if the test uses t.Parallel, then there's only so much goleak can do, and you should probably use VerifyTestMain in that case.

Long-term, if the runtime gives us the appropriate information to correlate the goroutine to the test that spawned it, we should definitely try to use that.

JacobOaks added a commit to JacobOaks/goleak that referenced this issue Oct 12, 2023
It's a known issue that goleak is incompatible with tests being run in parallel.
* uber-go#16
* uber-go#83

Unfortunately, there is no real solution to this issue.
* uber-go#16 (comment)
* uber-go#83 (comment)
* uber-go#83 (comment)

This PR at least documents the incompatibility
and suggests using `VerifyTestMain` instead.
sywhang pushed a commit that referenced this issue Oct 12, 2023
It's a known issue that goleak is incompatible with tests being run in
parallel.
* #16
* #83

Unfortunately, there is no real solution to this issue.
* #16 (comment)
* #83 (comment)
* #83 (comment)

This PR at least documents the incompatibility
and suggests using `VerifyTestMain` instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants