// If nil, the default dialer is used.
Dial func(ctx context.Context, network, address string) (Conn, error)
+ // lookupGroup merges LookupIPAddr calls together for lookups for the same
+ // host. The lookupGroup key is the LookupIPAddr.host argument.
+ // The return values are ([]IPAddr, error).
+ lookupGroup singleflight.Group
+
// TODO(bradfitz): optional interface impl override hook
// TODO(bradfitz): Timeout time.Duration?
}
func (r *Resolver) preferGo() bool { return r != nil && r.PreferGo }
func (r *Resolver) strictErrors() bool { return r != nil && r.StrictErrors }
+func (r *Resolver) getLookupGroup() *singleflight.Group {
+ if r == nil {
+ return &DefaultResolver.lookupGroup
+ }
+ return &r.lookupGroup
+}
+
// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
func LookupHost(host string) (addrs []string, err error) {
lookupGroupCtx, lookupGroupCancel := context.WithCancel(context.Background())
dnsWaitGroup.Add(1)
- ch, called := lookupGroup.DoChan(host, func() (interface{}, error) {
+ ch, called := r.getLookupGroup().DoChan(host, func() (interface{}, error) {
defer dnsWaitGroup.Done()
return testHookLookupIP(lookupGroupCtx, resolverFunc, host)
})
// let the lookup continue uncanceled, and let later
// lookups with the same key share the result.
// See issues 8602, 20703, 22724.
- if lookupGroup.ForgetUnshared(host) {
+ if r.getLookupGroup().ForgetUnshared(host) {
lookupGroupCancel()
} else {
go func() {
}
}
-// lookupGroup merges LookupIPAddr calls together for lookups
-// for the same host. The lookupGroup key is is the LookupIPAddr.host
-// argument.
-// The return values are ([]IPAddr, error).
-var lookupGroup singleflight.Group
-
// lookupIPReturn turns the return values from singleflight.Do into
// the return values from LookupIP.
func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IPAddr, error) {
"runtime"
"sort"
"strings"
+ "sync"
"testing"
"time"
)
t.Fatal(err)
}
}
+
+type lookupCustomResolver struct {
+ *Resolver
+ mu sync.RWMutex
+ dialed bool
+}
+
+func (lcr *lookupCustomResolver) dial() func(ctx context.Context, network, address string) (Conn, error) {
+ return func(ctx context.Context, network, address string) (Conn, error) {
+ lcr.mu.Lock()
+ lcr.dialed = true
+ lcr.mu.Unlock()
+ return Dial(network, address)
+ }
+}
+
+// TestConcurrentPreferGoResolversDial tests that multiple resolvers with the
+// PreferGo option used concurrently are all dialed properly.
+func TestConcurrentPreferGoResolversDial(t *testing.T) {
+ // The windows implementation of the resolver does not use the Dial
+ // function.
+ if runtime.GOOS == "windows" {
+ t.Skip("skip on windows")
+ }
+
+ testenv.MustHaveExternalNetwork(t)
+ testenv.SkipFlakyNet(t)
+
+ defer dnsWaitGroup.Wait()
+
+ resolvers := make([]*lookupCustomResolver, 2)
+ for i := range resolvers {
+ cs := lookupCustomResolver{Resolver: &Resolver{PreferGo: true}}
+ cs.Dial = cs.dial()
+ resolvers[i] = &cs
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(len(resolvers))
+ for i, resolver := range resolvers {
+ go func(r *Resolver, index int) {
+ defer wg.Done()
+ _, err := r.LookupIPAddr(context.Background(), "google.com")
+ if err != nil {
+ t.Fatalf("lookup failed for resolver %d: %q", index, err)
+ }
+ }(resolver.Resolver, i)
+ }
+ wg.Wait()
+
+ for i, resolver := range resolvers {
+ if !resolver.dialed {
+ t.Errorf("custom resolver %d not dialed during lookup", i)
+ }
+ }
+}