Skip to content

net: A lookup performed by a canceled context might affect subsequent lookups #22724

@tt

Description

@tt

What version of Go are you using (go version)?

go version go1.9.2 darwin/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/tt"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.9.2/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.9.2/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/v3/slr36r597rn0rrfzq_my6nv00000gn/T/go-build723069031=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"

What did you do?

package main

import (
	"context"
	"fmt"
	"net"
	"testing"
)

func TestLookupIPAddr(t *testing.T) {
	ctx := context.Background()
	canceledCtx, cancel := context.WithCancel(ctx)
	cancel()

	var resolver net.Resolver

	_, err := resolver.LookupIPAddr(canceledCtx, "example.com")
	if fmt.Sprintf("%#v", err) != `&errors.errorString{s:"operation was canceled"}` {
		t.Fatalf("unexpected error: %q", err)
	}

	_, err = resolver.LookupIPAddr(ctx, "example.com")
	if err != nil {
		t.Fatalf("unexpected error: %q", err)
	}
}

(This is a reduced example; it happened to me through the use of net/http.)

What did you expect to see?

The test should produce no output.

Resolving the IP address using a canceled context should fail and the following attempt to resolve the IP address using a non-canceled context should succeed.

What did you see instead?

The test produces the following output:

--- FAIL: TestLookupIPAddr (0.00s)
	lookup_test.go:24: unexpected error: "lookup example.com on 89.233.43.71:53: dial udp 89.233.43.71:53: operation was canceled"
FAIL

That is, the second attempt to resolve the IP address using the non-canceled context returns the result of the previous invocation.

This is happening as parallel lookups for the same hosts only execute once:

go/src/net/lookup.go

Lines 220 to 224 in bb3be40

// 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

... and following the first lookup, this goroutine exists:

1 @ 0x101298e 0x100535e 0x1188023 0x117c53d 0x118d9f6 0x1188982 0x118ae0c 0x115fade 0x105ca41
#	0x1188022	net.cgoLookupIP+0x72				/usr/local/Cellar/go/1.9.2/libexec/src/net/cgo_unix.go:212
#	0x117c53c	net.(*Resolver).lookupIP+0x12c			/usr/local/Cellar/go/1.9.2/libexec/src/net/lookup_unix.go:95
#	0x118d9f5	net.(*Resolver).(net.lookupIP)-fm+0x55		/usr/local/Cellar/go/1.9.2/libexec/src/net/lookup.go:187
#	0x1188981	net.glob..func10+0x51				/usr/local/Cellar/go/1.9.2/libexec/src/net/hook.go:19
#	0x118ae0b	net.(*Resolver).LookupIPAddr.func1+0x5b		/usr/local/Cellar/go/1.9.2/libexec/src/net/lookup.go:193
#	0x115fadd	internal/singleflight.(*Group).doCall+0x2d	/usr/local/Cellar/go/1.9.2/libexec/src/internal/singleflight/singleflight.go:93

Sleeping for just a millisecond between the two lookups allow the goroutine to complete and therefore resolves the IP address properly.

(It makes no difference if you re-initialize net.Resolver as the singleflight.Group variable is shared across instances.)

It's easy to fix this specific example (a sequential lookup) by "forgetting" the result if the context is canceled (similar to the solution applied in 77595e4 for #8602). I have a change for this and will submit it.

I do wonder if it would be more correct to never pass the context to the inner lookup; otherwise a goroutine that is canceled or times out will be able to affect other goroutines waiting for that same response.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions