Skip to content

sync: Once panics if testing.T.FailX or testing.T.SkipX are called #73159

@fmoor

Description

@fmoor

Go version

go version go1.24.2 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/fmoor/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/fmoor/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3782144130=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/fmoor/src/proj/go.mod'
GOMODCACHE='/home/fmoor/.go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/fmoor/.go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/fmoor/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.2'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

package main

import (
        "sync"
        "testing"
)

func Setup(t *testing.T) func() {
        return sync.OnceFunc(func() {
                t.SkipNow()
        })
}

func TestA(t *testing.T) {
        future := Setup(t)
        future()
}

What did you see happen?

$ go test -v
=== RUN   Test
--- FAIL: Test (0.00s)
panic: panic called with nil argument [recovered]
        panic: panic called with nil argument

goroutine 18 [running]:
testing.tRunner.func1.2({0x550e20, 0x6c98a0})
        /usr/local/go/src/testing/testing.go:1734 +0x21c
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1737 +0x35e
panic({0x0?, 0x0?})
        /usr/local/go/src/runtime/panic.go:792 +0x132
example%2ecom.Test.Setup.OnceFunc.func2.1()
        /usr/local/go/src/sync/oncefunc.go:24 +0x69
runtime.Goexit()
        /usr/local/go/src/runtime/panic.go:636 +0x5e
testing.(*common).SkipNow(0xc000102700)
        /usr/local/go/src/testing/testing.go:1156 +0x45
example%2ecom.Test.Setup.func1()
        /home/fmoor/src/proj/example_test.go:10 +0x19
example%2ecom.Test.Setup.OnceFunc.func2()
        /usr/local/go/src/sync/oncefunc.go:27 +0x62
sync.(*Once).doSlow(0x66b?, 0x66a?)
        /usr/local/go/src/sync/once.go:78 +0xab
sync.(*Once).Do(...)
        /usr/local/go/src/sync/once.go:69
example%2ecom.Test.Setup.OnceFunc.func3(...)
        /usr/local/go/src/sync/oncefunc.go:32
example%2ecom.Test(0xc000102700?)
        /home/fmoor/src/proj/example_test.go:16 +0x85
testing.tRunner(0xc000102700, 0x5842a0)
        /usr/local/go/src/testing/testing.go:1792 +0xf4
created by testing.(*T).Run in goroutine 1
        /usr/local/go/src/testing/testing.go:1851 +0x413
exit status 2
FAIL    example.com     0.004s

What did you expect to see?

sync.Once seems to incorrectly infer a panic when runtime.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

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.compiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Status

    Todo

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions