Repository: go
Issue: net/http: make Transport's idle connection management aware of DNS changes?
Labels: NeedsFix



### What version of Go are you using (`go version`)?

go version go1.9 linux/amd64

and 

go version go1.9.2 linux/amd64

### Does this issue reproduce with the latest release?

Yes

### What operating system and processor architecture are you using (`go env`)?

% go env                                                             
GOARCH="amd64"
GOBIN="/home/sszuecs/go/bin"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/sszuecs/go"
GORACE=""
GOROOT="/usr/share/go"
GOTOOLDIR="/usr/share/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build505089582=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
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?

I am running `go run main.go` and change the /etc/hosts to change www.google.de to 127.0.0.1.
Depending on  the order it fails to switch DNS after IdleConnTimeout or it will never request DNS again. DNS will be tried to lookup in case you first target it to 127.0.0.1 and after that comment the entry in /etc/hosts. The problem is that if you want to change your target loadbalancers via DNS lookup, this will not be done. The workaround is commented in the code, which reliably will do the DNS failover. The problem seems to be that IdleConnTimeout is bigger then the time.Sleep duration in the code, which you can also change to see that this works. In case of being an edge proxy with high number of requests, the case IdleConnTimeout < process-next-request will never happen.


    package main
    
    import (
    	"log"
    	"net"
    	"net/http"
    	"time"
    )
    
    func main() {
    
    	tr := &http.Transport{
    		DialContext: (&net.Dialer{
    			Timeout:   5 * time.Second,
    			KeepAlive: 30 * time.Second,
    			DualStack: true,
    		}).DialContext,
    		TLSHandshakeTimeout: 5 * time.Second,
    		IdleConnTimeout:     5 * time.Second,
    	}
    
    	go func(rt http.RoundTripper) {
    		for {
    			time.Sleep(1 * time.Second)
    
    			req, err := http.NewRequest("GET", "https://www.google.de/", nil)
    			if err != nil {
    				log.Printf("Failed to do request: %v", err)
    				continue
    			}
    
    			resp, err := rt.RoundTrip(req)
    			if err != nil {
    				log.Printf("Failed to do roundtrip: %v", err)
    				continue
    			}
    			//io.Copy(ioutil.Discard, resp.Body)
    			resp.Body.Close()
    			log.Printf("resp status: %v", resp.Status)
    		}
    	}(tr)
    
    	// FIX:
    	// go func(transport *http.Transport) {
    	// 	for {
    	// 		time.Sleep(3 * time.Second)
    	// 		transport.CloseIdleConnections()
    	// 	}
    	// }(tr)
    
    	ch := make(chan struct{})
    	<-ch
    }


### What did you expect to see?

I want to to see that IdleConnTimeout will reliably close idle connections, such that DNS is queried for new connections again, similar to the goroutine case in the code. We need to slowly being able to fade traffic.

### What did you see instead?

In case you start the application with /etc/hosts entry is not set, and then change it, it will never fail the request, so the new DNS lookup is not being made.