The DNS code can start goroutines and not wait for them to complete.
This does no harm, but in tests this can cause a race condition with
the test hooks that are installed and unintalled around the tests.
Add a WaitGroup that tests of DNS can use to avoid the race.
Fixes #21090
Change-Id: I6c1443a9c2378e8b89d0ab1d6390c0e3e726b0ce
Reviewed-on: https://go-review.googlesource.com/82795
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
}
// DoChan is like Do but returns a channel that will receive the
-// results when they are ready.
-func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
+// results when they are ready. The second result is true if the function
+// will eventually be called, false if it will not (because there is
+// a pending request with this key).
+func (g *Group) DoChan(key string, fn func() (interface{}, error)) (<-chan Result, bool) {
ch := make(chan Result, 1)
g.mu.Lock()
if g.m == nil {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
- return ch
+ return ch, false
}
c := &call{chans: []chan<- Result{ch}}
c.wg.Add(1)
go g.doCall(c, key, fn)
- return ch
+ return ch, true
}
// doCall handles the single call for a key.
)
func TestCgoLookupIP(t *testing.T) {
+ defer dnsWaitGroup.Wait()
ctx := context.Background()
_, err, ok := cgoLookupIP(ctx, "localhost")
if !ok {
}
func TestCgoLookupIPWithCancel(t *testing.T) {
+ defer dnsWaitGroup.Wait()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err, ok := cgoLookupIP(ctx, "localhost")
}
func TestCgoLookupPort(t *testing.T) {
+ defer dnsWaitGroup.Wait()
ctx := context.Background()
_, err, ok := cgoLookupPort(ctx, "tcp", "smtp")
if !ok {
}
func TestCgoLookupPortWithCancel(t *testing.T) {
+ defer dnsWaitGroup.Wait()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err, ok := cgoLookupPort(ctx, "tcp", "smtp")
}
func TestCgoLookupPTR(t *testing.T) {
+ defer dnsWaitGroup.Wait()
ctx := context.Background()
_, err, ok := cgoLookupPTR(ctx, "127.0.0.1")
if !ok {
}
func TestCgoLookupPTRWithCancel(t *testing.T) {
+ defer dnsWaitGroup.Wait()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
_, err, ok := cgoLookupPTR(ctx, "127.0.0.1")
var lastErr error
for _, fqdn := range conf.nameList(name) {
for _, qtype := range qtypes {
+ dnsWaitGroup.Add(1)
go func(qtype uint16) {
+ defer dnsWaitGroup.Done()
cname, rrs, err := r.tryOneName(ctx, conf, fqdn, qtype)
lane <- racer{cname, rrs, err}
}(qtype)
// Issue 13705: don't try to resolve onion addresses, etc
func TestLookupTorOnion(t *testing.T) {
+ defer dnsWaitGroup.Wait()
r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
addrs, err := r.LookupIPAddr(context.Background(), "foo.onion")
if err != nil {
}
func TestUpdateResolvConf(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
r := Resolver{PreferGo: true, Dial: fakeDNSServerSuccessful.DialContext}
conf, err := newResolvConfTest()
}
func TestGoLookupIPWithResolverConfig(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
fake := fakeDNSServer{func(n, s string, q *dnsMsg, _ time.Time) (*dnsMsg, error) {
switch s {
case "[2001:4860:4860::8888]:53", "8.8.8.8:53":
// Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
fake := fakeDNSServer{func(n, s string, q *dnsMsg, tm time.Time) (*dnsMsg, error) {
r := &dnsMsg{
dnsMsgHdr: dnsMsgHdr{
// querying the original name instead of an error encountered
// querying a generated name.
func TestErrorForOriginalNameWhenSearching(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
const fqdn = "doesnotexist.domain"
conf, err := newResolvConfTest()
// Issue 15434. If a name server gives a lame referral, continue to the next.
func TestIgnoreLameReferrals(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
// Issue 16865. If a name server times out, continue to the next.
func TestRetryTimeout(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
}
func testRotate(t *testing.T, rotate bool, nameservers, wantServers []string) {
+ defer dnsWaitGroup.Wait()
+
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
// Issue 17448. With StrictErrors enabled, temporary errors should make
// LookupIP fail rather than return a partial result.
func TestStrictErrorsLookupIP(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
// Issue 17448. With StrictErrors enabled, temporary errors should make
// LookupTXT stop walking the search list.
func TestStrictErrorsLookupTXT(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
conf, err := newResolvConfTest()
if err != nil {
t.Fatal(err)
}
}
}
+
+// Test for a race between uninstalling the test hooks and closing a
+// socket connection. This used to fail when testing with -race.
+func TestDNSGoroutineRace(t *testing.T) {
+ defer dnsWaitGroup.Wait()
+
+ fake := fakeDNSServer{func(n, s string, q *dnsMsg, t time.Time) (*dnsMsg, error) {
+ time.Sleep(10 * time.Microsecond)
+ return nil, poll.ErrTimeout
+ }}
+ r := Resolver{PreferGo: true, Dial: fake.DialContext}
+
+ // The timeout here is less than the timeout used by the server,
+ // so the goroutine started to query the (fake) server will hang
+ // around after this test is done if we don't call dnsWaitGroup.Wait.
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Microsecond)
+ defer cancel()
+ _, err := r.LookupIPAddr(ctx, "where.are.they.now")
+ if err == nil {
+ t.Fatal("fake DNS lookup unexpectedly succeeded")
+ }
+}
"context"
"internal/nettrace"
"internal/singleflight"
+ "sync"
)
// protocols contains minimal mappings between internet protocol
},
}
+// dnsWaitGroup can be used by tests to wait for all DNS goroutines to
+// complete. This avoids races on the test hooks.
+var dnsWaitGroup sync.WaitGroup
+
const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow
func lookupProtocolMap(name string) (int, error) {
resolverFunc = alt
}
- ch := lookupGroup.DoChan(host, func() (interface{}, error) {
+ dnsWaitGroup.Add(1)
+ ch, called := lookupGroup.DoChan(host, func() (interface{}, error) {
+ defer dnsWaitGroup.Done()
return testHookLookupIP(ctx, resolverFunc, host)
})
+ if !called {
+ dnsWaitGroup.Done()
+ }
select {
case <-ctx.Done():
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupGmailMXTests {
mxs, err := LookupMX(tt.name)
if err != nil {
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupGmailNSTests {
nss, err := LookupNS(tt.name)
if err != nil {
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupGmailTXTTests {
txts, err := LookupTXT(tt.name)
if err != nil {
t.Skip("both IPv4 and IPv6 are required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupGooglePublicDNSAddrTests {
names, err := LookupAddr(tt.addr)
if err != nil {
t.Skip("IPv6 is required")
}
+ defer dnsWaitGroup.Wait()
+
addrs, err := LookupHost("localhost")
if err != nil {
t.Fatal(err)
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupCNAMETests {
cname, err := LookupCNAME(tt.name)
if err != nil {
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupGoogleHostTests {
addrs, err := LookupHost(tt.name)
if err != nil {
testenv.MustHaveExternalNetwork(t)
}
+ defer dnsWaitGroup.Wait()
+
txts, err := LookupTXT("golang.rsc.io")
if err != nil {
t.Fatal(err)
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
for _, tt := range lookupGoogleIPTests {
ips, err := LookupIP(tt.name)
if err != nil {
}
func TestReverseAddress(t *testing.T) {
+ defer dnsWaitGroup.Wait()
for i, tt := range revAddrTests {
a, err := reverseaddr(tt.Addr)
if len(tt.ErrPrefix) > 0 && err == nil {
t.Skip("test disabled; use -dnsflood to enable")
}
+ defer dnsWaitGroup.Wait()
+
var N = 5000
if runtime.GOOS == "darwin" {
// On Darwin this test consumes kernel threads much
testenv.MustHaveExternalNetwork(t)
}
+ defer dnsWaitGroup.Wait()
+
for i, fn := range []func() func(){forceGoDNS, forceCgoDNS} {
fixup := fn()
if fixup == nil {
t.Skip("IPv4 is required")
}
+ defer dnsWaitGroup.Wait()
+
if fixup := forceGoDNS(); fixup != nil {
testDots(t, "go")
fixup()
if runtime.GOOS == "nacl" {
t.Skip("skip on nacl")
}
+
+ defer dnsWaitGroup.Wait()
+
if fixup := forceGoDNS(); fixup != nil {
defer fixup()
}
)
func TestGoLookupIP(t *testing.T) {
+ defer dnsWaitGroup.Wait()
host := "localhost"
ctx := context.Background()
_, err, ok := cgoLookupIP(ctx, host)