Open
Description
What version of Go are you using (go version
)?
$ go version go version go1.18 darwin/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 go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/Users/liggitt/Library/Caches/go-build" GOENV="/Users/liggitt/Library/Application Support/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/liggitt/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="/Users/liggitt/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/Users/liggitt/.gvm/gos/go1.18" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/Users/liggitt/.gvm/gos/go1.18/pkg/tool/darwin_amd64" GOVCS="" GOVERSION="go1.18" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="/Users/liggitt/go/src/k8s.io/kubernetes/go.mod" GOWORK="" 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/7f/9xt_73f12xlby0w362rgk0s400kjgb/T/go-build2769859399=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
Make a request with a resettable body, if the response is a 429, reset the body and repeat the request.
Reported in kubernetes/kubernetes#108906, standalone reproducer here:
package mytest
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestSeek(t *testing.T) {
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
req.Body.Close()
http.Error(w, "try again", http.StatusTooManyRequests)
}))
defer testServer.Close()
client := &http.Client{}
body := bytes.NewReader([]byte(strings.Repeat("abcd", 1000)))
req, err := http.NewRequest("POST", testServer.URL, body)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 1000; i++ {
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
// drain and close the response body to complete the request before reusing the request body
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
body.Seek(0, 0)
}
}
What did you expect to see?
Success, as in go1.17 and earlier releases
$ go version && go test -c -race my_test.go && stress ./mytest.test
go version go1.17.8 darwin/amd64
72 runs so far, 0 failures
151 runs so far, 0 failures
214 runs so far, 0 failures
...
3084 runs so far, 0 failures
^C
What did you see instead?
Data race between the body reset and reads from the request body called from net/http.(*transferWriter).writeBody()
$ go version && go test -c -race my_test.go && stress ./mytest.test
go version go1.18 darwin/amd64
73 runs so far, 0 failures
152 runs so far, 0 failures
/var/folders/7f/9xt_73f12xlby0w362rgk0s400kjgb/T/go-stress-20220325T130052-929070282
==================
WARNING: DATA RACE
Read at 0x00c00007ef78 by goroutine 13:
bytes.(*Reader).Read()
/Users/liggitt/.gvm/gos/go1.18/src/bytes/reader.go:41 +0x58
io.(*nopCloser).Read()
<autogenerated>:1 +0x76
io.discard.ReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:610 +0x91
io.(*discard).ReadFrom()
<autogenerated>:1 +0x5d
io.copyBuffer()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:412 +0x1c2
io.Copy()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:385 +0x64
net/http.(*transferWriter).doBodyCopy()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transfer.go:411 +0x3e
net/http.(*transferWriter).writeBody()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transfer.go:374 +0x7ef
net/http.(*Request).write()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/request.go:698 +0x1197
net/http.(*persistConn).writeLoop()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transport.go:2395 +0x2e4
net/http.(*Transport).dialConn.func6()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transport.go:1751 +0x39
Previous write at 0x00c00007ef78 by goroutine 91:
bytes.(*Reader).Read()
/Users/liggitt/.gvm/gos/go1.18/src/bytes/reader.go:46 +0x11c
io.(*nopCloser).Read()
<autogenerated>:1 +0x76
io.(*LimitedReader).Read()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:476 +0xc5
io.copyBuffer()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:426 +0x28a
io.Copy()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:385 +0x8d
net.genericReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/net.go:662 +0x28
net.(*TCPConn).readFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/tcpsock_posix.go:54 +0xac
net.(*TCPConn).ReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/tcpsock.go:130 +0x68
io.copyBuffer()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:412 +0x1c2
io.Copy()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:385 +0x86
net/http.persistConnWriter.ReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transport.go:17
…
/var/folders/7f/9xt_73f12xlby0w362rgk0s400kjgb/T/go-stress-20220325T130052-124085153
==================
WARNING: DATA RACE
Read at 0x00c0000a0f78 by goroutine 13:
bytes.(*Reader).Read()
/Users/liggitt/.gvm/gos/go1.18/src/bytes/reader.go:41 +0x58
io.(*nopCloser).Read()
<autogenerated>:1 +0x76
io.discard.ReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:610 +0x91
io.(*discard).ReadFrom()
<autogenerated>:1 +0x5d
io.copyBuffer()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:412 +0x1c2
io.Copy()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:385 +0x64
net/http.(*transferWriter).doBodyCopy()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transfer.go:411 +0x3e
net/http.(*transferWriter).writeBody()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transfer.go:374 +0x7ef
net/http.(*Request).write()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/request.go:698 +0x1197
net/http.(*persistConn).writeLoop()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transport.go:2395 +0x2e4
net/http.(*Transport).dialConn.func6()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transport.go:1751 +0x39
Previous write at 0x00c0000a0f78 by goroutine 92:
bytes.(*Reader).Read()
/Users/liggitt/.gvm/gos/go1.18/src/bytes/reader.go:46 +0x11c
io.(*nopCloser).Read()
<autogenerated>:1 +0x76
io.(*LimitedReader).Read()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:476 +0xc5
io.copyBuffer()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:426 +0x28a
io.Copy()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:385 +0x8d
net.genericReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/net.go:662 +0x28
net.(*TCPConn).readFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/tcpsock_posix.go:54 +0xac
net.(*TCPConn).ReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/tcpsock.go:130 +0x68
io.copyBuffer()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:412 +0x1c2
io.Copy()
/Users/liggitt/.gvm/gos/go1.18/src/io/io.go:385 +0x86
net/http.persistConnWriter.ReadFrom()
/Users/liggitt/.gvm/gos/go1.18/src/net/http/transport.go:17
…
213 runs so far, 2 failures