-
Notifications
You must be signed in to change notification settings - Fork 18k
sync: Once panics if testing.T.FailX or testing.T.SkipX are called #73159
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
Comments
Looking at the implementation https://cs.opensource.google/go/go/+/refs/tags/go1.24.2:src/sync/oncefunc.go;l=11-37 |
I agree. Just today we are making a similar change in errgroup; see https://go.dev/cl/644575. |
For that you could use a double defer sandwich! |
Change https://go.dev/cl/662816 mentions this issue: |
See CL 662816 , this breaks the stack trace. |
Maybe we should do something differently on a Is the idea is that, if the OnceFunc exits the test, you'll never call it again, but if it returns normally you may call it more times within the same test? What should happen if a OnceFunc does a Goexit on the first call and then you call it again? I can see three possibilities:
I don't like option 3 because usually Goexit is preceded by somehow signaling why this goroutine is exiting, and subsequent calls wouldn't do that. For example, this happens in I'd be okay with options 1 or 2. |
My use case was to do expensive test fixture setup in another goroutine and return the fixture to the test using the function returned from sync.OnceValue. The goal is to allow for expensive fixtures to be created concurrently. The sync.OnceValue function also has to handle the case where the fixture setup failed. In that case it calls t.FailNow. func DBFixture(t *testing.T) func() *sql.DB {
connChan := make(chan *sql.DB, 1)
go func() {
// start a database and migrate it
connChan <- startDatabase()
}()
return sync.OnceValue(func() *sql.DB {
conn := <-connChan
if conn == nil {
t.Fatalf("database setup failed")
}
return conn
})
} |
It seems reasonable to me. Imagine your test passes some lambda into a library that calls it within a sync.Once to memoize an expensive computation. Now imagine that sometimes the lambda calls Fatal when it can't complete its task. The sync.Once is just a detail of the library. (A pedant might argue that the test shouldn't assume that the lambda will be called from the same goroutine, and that means it's not morally permitted to call Fatal--I am that pedant!--but in fact tests do this all the time.)
The idea is that the OnceFunc is a temporary variable created by the test. Nothing says a Once needs to have global extent.
I think it can't be option 1, because that would cause the testing package to report that the test panicked even though it actually just called t.Fatal from within the Once. And it shouldn't be option 3 for the reason you gave. So that leaves option 2. Once.Do calls subsequent to a Goexit should never be allowed to happen in the scenario I imagined above; making them panic would at least give the user an informative error message explaining that they are misusing Once. |
Go version
go version go1.24.2 linux/amd64
Output of
go env
in your module/workspace:What did you do?
What did you see happen?
What did you expect to see?
sync.Once
seems to incorrectly infer a panic whenruntime.Goexit
is called. I would expect the test to be skipped instead of panic.$ go test -v === RUN Test --- SKIP: Test (0.00s) PASS ok example.com 0.001s
The text was updated successfully, but these errors were encountered: