Skip to content

net: lazily created semaphore channels associate with a synctest bubble #80903

Open

Description

@serhiy-bzhezytskyy

Go version

go version go1.26.6 darwin/arm64

Output of go env in your module/workspace

GOVERSION='go1.26.6'  GOOS='darwin'  GOARCH='arm64'  CGO_ENABLED='1'
GOEXPERIMENT=''  GODEBUG=''  GOFLAGS=''

Does this issue reproduce with the latest release?

Yes, on go1.26.6. Also at master 72aa6db, where the change below was tested.

What did you do?

Two net.LookupIP calls, the first inside a synctest bubble and the second outside it, with no dependencies:

package main

import (
	"net"
	"testing"
	"testing/synctest"
)

func TestBubbleFirstThenOutside(t *testing.T) {
	synctest.Test(t, func(t *testing.T) {
		net.LookupIP("localhost")
	})
	net.LookupIP("localhost")
}

The same two calls in the reverse order pass, which is what makes this an ordering problem rather than a bubble-versus-network one:

func TestOutsideFirstThenBubble(t *testing.T) {
	net.LookupIP("localhost")
	synctest.Test(t, func(t *testing.T) {
		net.LookupIP("localhost")
	})
}

What did you see happen?

The process aborts. Which object aborts depends on the resolver path.

Default (cgo) path, verbatim from go1.26.6 darwin/arm64:

fatal error: select on synctest channel from outside bubble

net.acquireThread({...})
	/tmp/gomodcache/golang.org/toolchain@v0.0.1-go1.26.6.darwin-arm64/src/net/net.go:813 +0xa0
net.doBlockingWithCtx[...](...)
	/tmp/gomodcache/golang.org/toolchain@v0.0.1-go1.26.6.darwin-arm64/src/net/cgo_unix.go:48 +0x58

With GODEBUG=netdns=go it is resolvConf.ch instead:

fatal error: send on synctest channel from outside bubble

net.(*resolverConfig).tryAcquireSema(...)
	/tmp/gomodcache/golang.org/toolchain@v0.0.1-go1.26.6.darwin-arm64/src/net/dnsclient_unix.go:430
net.(*resolverConfig).tryUpdate(...)
	/tmp/gomodcache/golang.org/toolchain@v0.0.1-go1.26.6.darwin-arm64/src/net/dnsclient_unix.go:396 +0x80

Three package-level channels in net are created inside a sync.Once, so each is associated with the bubble of the first caller to reach it: threadLimit in acquireThread, and resolvConf.ch and nssConfig.ch in their init methods, at dnsclient_unix.go:386 and nss.go:45 at master 72aa6db. nssConfig.ch is not in the traces only because resolvConf.ch aborts first; creating that one eagerly moves the abort into nss.go.

Because the association depends on which caller runs first, one test order runs to completion and another aborts the process. The abort is a fatal error rather than a panic, so no test is reported as failing.

What did you expect to see?

Both orders behaving the same way.

I think this is the case @prattmic raised in #67434, "lazy initialization of a global channel occurring inside the bubble and thus panicking when used outside", and @neild's answer there — "ensure any lazy initialization happens outside the bubble" — applies to two of the three. Both are one-slot semaphores, so they can be created in their var initializers:

var resolvConf = resolverConfig{ch: make(chan struct{}, 1)}
var nssConfig = nsswitchConfig{ch: make(chan struct{}, 1)}

I tried that at master 72aa6db. go test -short net passes before and after, the full suite shows no new failures, and with GODEBUG=netdns=go the test above goes from fatal to passing.

threadLimit I left alone. Its capacity comes from rlim.Cur, and a program may lower RLIMIT_NOFILE after package initialization, so eager creation could allow more concurrent getaddrinfo calls than there are descriptors. With the other two changed, the cgo path still aborts in acquireThread.

Activity

  1. gopherbot commented on Aug 16, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/816200 mentions this issue: net: create resolver semaphore channels eagerly

  2. dr2chase commented on Aug 26, 2026

    @dr2chase
    Contributor
  3. added
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions