Skip to content

net/http, x/net/http2: support http2requestBody.Close() being called multiple times concurrently #51197

@moshegood

Description

@moshegood

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

$ go version
go version go1.17.6 darwin/amd64

Does this issue reproduce with the latest release?

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

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/moshe/Library/Caches/go-build"
GOENV="/Users/moshe/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/moshe/code/go/pkg/mod"
GONOPROXY="git.sqcorp.co,git.corp.squareup.com,github.com/squareup"
GONOSUMDB="git.sqcorp.co,git.corp.squareup.com,github.com/squareup"
GOOS="darwin"
GOPATH="/Users/moshe/code/go"
GOPRIVATE="git.sqcorp.co,git.corp.squareup.com,github.com/squareup"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/opt/go/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/opt/go/libexec/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.17.6"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/usr/local/Cellar/go/1.17.6/libexec/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 -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/k0/pqfxzl896xg7g6vq_wby46380000gn/T/go-build4218274825=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Used net/http2 with net/httputil/ReverseProxy.
Then ran a test with the -race flag.

What did you expect to see?

No race conditions.

What did you see instead?

Race conditions.

Not quite sure how hard this will be to replicate cleanly to reproduce.
Basically, the http2 portion of net/http will close a request body when it’s done.
Also, the ProxyServer portion of net/http/httputil will close a request body when it’s done.
So the request body gets closed twice.
But the http2requestBody.Close() method, while obviously coded to support being called multiple times, will run into a race condition if Close() is called twice concurrently.

Very simple fix as well.
Current code:

// requestBody is the Handler's Request.Body type.
// Read and Close may be called concurrently.
type http2requestBody struct {
	_             http2incomparable
	stream        *http2stream
	conn          *http2serverConn
	closed        bool       // for use by Close only
	sawEOF        bool       // for use by Read only
	pipe          *http2pipe // non-nil if we have a HTTP entity message body
	needsContinue bool       // need to send a 100-continue
}

func (b *http2requestBody) Close() error {
	if b.pipe != nil && !b.closed {
		b.pipe.BreakWithError(http2errClosedBody)
	}
	b.closed = true
	return nil
}

Fixed code:

// requestBody is the Handler's Request.Body type.
// Read and Close may be called concurrently.
type http2requestBody struct {
	_             http2incomparable
	stream        *http2stream
	conn          *http2serverConn
	closeOnce     sync.Once       // for use by Close only
	sawEOF        bool       // for use by Read only
	pipe          *http2pipe // non-nil if we have a HTTP entity message body
	needsContinue bool       // need to send a 100-continue
}

func (b *http2requestBody) Close() error {
	b.closeOnce.Do(func(){
		if b.pipe != nil {
			b.pipe.BreakWithError(http2errClosedBody)
		}
	})
	return nil
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions