Commit bd859439 authored by Ian Lance Taylor's avatar Ian Lance Taylor

net: don't let cancelation of a DNS lookup affect another lookup

Updates #8602
Updates #20703
Fixes #22724

Change-Id: I27b72311b2c66148c59977361bd3f5101e47b51d
Reviewed-on: https://go-review.googlesource.com/100840
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 0b20aece
...@@ -103,11 +103,21 @@ func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) { ...@@ -103,11 +103,21 @@ func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
g.mu.Unlock() g.mu.Unlock()
} }
// Forget tells the singleflight to forget about a key. Future calls // ForgetUnshared tells the singleflight to forget about a key if it is not
// to Do for this key will call the function rather than waiting for // shared with any other goroutines. Future calls to Do for a forgotten key
// an earlier call to complete. // will call the function rather than waiting for an earlier call to complete.
func (g *Group) Forget(key string) { // Returns whether the key was forgotten or unknown--that is, whether no
// other goroutines are waiting for the result.
func (g *Group) ForgetUnshared(key string) bool {
g.mu.Lock() g.mu.Lock()
defer g.mu.Unlock()
c, ok := g.m[key]
if !ok {
return true
}
if c.dups == 0 {
delete(g.m, key) delete(g.m, key)
g.mu.Unlock() return true
}
return false
} }
...@@ -194,10 +194,16 @@ func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, err ...@@ -194,10 +194,16 @@ func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, err
resolverFunc = alt resolverFunc = alt
} }
// We don't want a cancelation of ctx to affect the
// lookupGroup operation. Otherwise if our context gets
// canceled it might cause an error to be returned to a lookup
// using a completely different context.
lookupGroupCtx, lookupGroupCancel := context.WithCancel(context.Background())
dnsWaitGroup.Add(1) dnsWaitGroup.Add(1)
ch, called := lookupGroup.DoChan(host, func() (interface{}, error) { ch, called := lookupGroup.DoChan(host, func() (interface{}, error) {
defer dnsWaitGroup.Done() defer dnsWaitGroup.Done()
return testHookLookupIP(ctx, resolverFunc, host) return testHookLookupIP(lookupGroupCtx, resolverFunc, host)
}) })
if !called { if !called {
dnsWaitGroup.Done() dnsWaitGroup.Done()
...@@ -205,20 +211,28 @@ func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, err ...@@ -205,20 +211,28 @@ func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, err
select { select {
case <-ctx.Done(): case <-ctx.Done():
// If the DNS lookup timed out for some reason, force // Our context was canceled. If we are the only
// future requests to start the DNS lookup again // goroutine looking up this key, then drop the key
// rather than waiting for the current lookup to // from the lookupGroup and cancel the lookup.
// complete. See issue 8602. // If there are other goroutines looking up this key,
ctxErr := ctx.Err() // let the lookup continue uncanceled, and let later
if ctxErr == context.DeadlineExceeded { // lookups with the same key share the result.
lookupGroup.Forget(host) // See issues 8602, 20703, 22724.
if lookupGroup.ForgetUnshared(host) {
lookupGroupCancel()
} else {
go func() {
<-ch
lookupGroupCancel()
}()
} }
err := mapErr(ctxErr) err := mapErr(ctx.Err())
if trace != nil && trace.DNSDone != nil { if trace != nil && trace.DNSDone != nil {
trace.DNSDone(nil, false, err) trace.DNSDone(nil, false, err)
} }
return nil, err return nil, err
case r := <-ch: case r := <-ch:
lookupGroupCancel()
if trace != nil && trace.DNSDone != nil { if trace != nil && trace.DNSDone != nil {
addrs, _ := r.Val.([]IPAddr) addrs, _ := r.Val.([]IPAddr)
trace.DNSDone(ipAddrsEface(addrs), r.Shared, r.Err) trace.DNSDone(ipAddrsEface(addrs), r.Shared, r.Err)
......
...@@ -789,3 +789,28 @@ func TestLookupNonLDH(t *testing.T) { ...@@ -789,3 +789,28 @@ func TestLookupNonLDH(t *testing.T) {
t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost) t.Fatalf("lookup error = %v, want %v", err, errNoSuchHost)
} }
} }
func TestLookupContextCancel(t *testing.T) {
if testenv.Builder() == "" {
testenv.MustHaveExternalNetwork(t)
}
if runtime.GOOS == "nacl" {
t.Skip("skip on nacl")
}
defer dnsWaitGroup.Wait()
ctx, ctxCancel := context.WithCancel(context.Background())
ctxCancel()
_, err := DefaultResolver.LookupIPAddr(ctx, "google.com")
if err != errCanceled {
testenv.SkipFlakyNet(t)
t.Fatal(err)
}
ctx = context.Background()
_, err = DefaultResolver.LookupIPAddr(ctx, "google.com")
if err != nil {
testenv.SkipFlakyNet(t)
t.Fatal(err)
}
}
...@@ -87,6 +87,7 @@ func TestTCPSpuriousConnSetupCompletionWithCancel(t *testing.T) { ...@@ -87,6 +87,7 @@ func TestTCPSpuriousConnSetupCompletionWithCancel(t *testing.T) {
if testenv.Builder() == "" { if testenv.Builder() == "" {
testenv.MustHaveExternalNetwork(t) testenv.MustHaveExternalNetwork(t)
} }
defer dnsWaitGroup.Wait()
t.Parallel() t.Parallel()
const tries = 10000 const tries = 10000
var wg sync.WaitGroup var wg sync.WaitGroup
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment