Description
What version of Go are you using (go version
)?
$ go version go version go1.17.3 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="" GOARCH="amd64" GOBIN="" GOCACHE="/home/eg/.cache/go-build" GOENV="/home/eg/.config/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/home/eg/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/eg/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/lib/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.17.3" GCCGO="gccgo" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/usr/lib/go/src/go.mod" 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-build2074510398=/tmp/go-build -gno-record-gcc-switches"
What did you do?
- Wrap
net.Listener
with error injections - Run
http.Server
using such listener - Run many concurrent requests targeting such server
- On every request, at the client side, wait for request body to be closed
Kind of a minimal example to run described steps: https://play.golang.com/p/lku8lEgiPu6
Yeah, I know that reproducer feels big, but the most of it is to create error injections actually, but
main part (see main fn) is straightforward and simple.
What did you expect to see?
I expect that net/http
eventually closes Request.Body
as it is told in documentation for RoundTripper: https://pkg.go.dev/net/http#RoundTripper
What did you see instead?
Specified example program hangs indefinitely while waiting for Request.Body
to be closed.
Investigation
- It is possible that encountered behavior has something to deal with
errServerClosedIdle
error insidenet/http
, because we have met described hangs only after receiving that error fromClient.Do
. - Also the fact that I am doing
Post
request may be important becausenet/http
does not retryPost
(non-idepotent) request on such error.
In case next assumption is true:
Once request execution got to func (pc *persistConn) roundTrip(req *transportRequest)
call, Request.Body
closure is expected to be done by func (pc *persistConn) writeLoop()
.
Then it seems like there is a possibility of not closing body in the writeLoop
in case pc
is closed concurrently.
Imagine:
---------------------------------------------------------------------------------------> time
gor1 pc.writech <- writeRequest{...}
gor2 pc.closeWithError(...)
writeLoop select { case <-pc.closech: return }
So request was sent successfully into pc.writech
(channel has buffer of size 1), but won't be ever read from it because writeLoop
exited.
Related places in the code:
Metadata
Metadata
Assignees
Labels
Type
Projects
Status