Skip to content

net/http: zombie connection leaked after Server.IdleTimeout #51614

Closed
@lesismal

Description

@lesismal

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

$ go version
go version go1.17 linux/amd64

Does this issue reproduce with the latest release?

Yes.

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

go env Output
$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/ubuntu/.cache/go-build"
GOENV="/home/ubuntu/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/ubuntu/dev/gopath/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/ubuntu/dev/gopath"
GOPRIVATE=""
GOPROXY="direct"
GOROOT="/home/ubuntu/dev/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/ubuntu/dev/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.17"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2048112507=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Here are the code that can reproduce it:

// server.go
package main

import (
	"io"
	"net/http"
	_ "net/http/pprof"
	"time"
)

func main() {
	mux := &http.ServeMux{}
	mux.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		data, _ := io.ReadAll(r.Body)
		w.Write(data)
	})

	server := http.Server{
		Addr:        "localhost:8080",
		Handler:     mux,

		// Set IdleTimeout to 3s
		IdleTimeout: 3 * time.Second,
	}
	server.ListenAndServe()
}
// client.go
package main

import (
	"log"
	"net"
	"time"
)

func main() {
	go func() {
		for i := 0; true; i++ {
			// Keep printing alive until the conn is closed and the process exit
			log.Printf("alive %vs", i)
			time.Sleep(time.Second)
		}
	}()

	conn, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		panic(err)
	}

	// step 1: Send the first request
	reqData := []byte("POST /echo HTTP/1.1\r\nHost: localhost:8080\r\nContent-Length: 5\r\nAccept-Encoding: gzip\r\n\r\nhello")
	_, err = conn.Write(reqData)
	if err != nil {
		panic(err)
	}

	// step 2: Recv the first response
	resData := make([]byte, 1024)
	n, err := conn.Read(resData)
	if err != nil {
		panic(err)
	}
	log.Printf("resData: \n-----\n%v\n-----\nerror: %v", string(resData[:n]), err)

	// step 3: Send 4 bytes to server, no longer send the left data of a full request
	_, err = conn.Write(reqData[:4])
	if err != nil {
		log.Fatal(err)
	}

	// step 4: Read and wait the server close the conn.
	// The server will not close the conn and will block here
	_, err = conn.Read(resData)
	if err != nil {
		log.Fatal(err)
	}
}
# client output
2022/03/11 21:39:44 resData:
-----
HTTP/1.1 200 OK
Date: Fri, 11 Mar 2022 13:39:44 GMT
Content-Length: 5
Content-Type: text/plain; charset=utf-8

hello
-----
error: <nil>
2022/03/11 21:39:44 alive 1s
2022/03/11 21:39:45 alive 2s
2022/03/11 21:39:46 alive 3s
...
2022/03/11 21:41:44 alive 120s
...
...

What did you expect to see?

The server should close the client conn when the client didn't send all the request data after Server.IdleTimeout

What did you see instead?

The server didn't close the clien conn

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions