Skip to content

Commit 13ea1fd

Browse files
committed
net/http, strings, bytes: fix http race, revert part of Reader behavior change
I fixed this data race regression in two ways: in net/http itself, and also partially reverting the change from https://golang.org/cl/77580046 . Previously a Read from a strings.Reader or bytes.Reader returning 0 bytes would not be a memory write. After 77580046 it was. This reverts that back in case others depended on that. Also adds tests. Fixes #7856 LGTM=ruiu, iant R=iant, ruiu CC=golang-codereviews, gri https://golang.org/cl/94740044
1 parent f40e574 commit 13ea1fd

File tree

7 files changed

+90
-8
lines changed

7 files changed

+90
-8
lines changed

src/pkg/bytes/reader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ func (r *Reader) Len() int {
3030
}
3131

3232
func (r *Reader) Read(b []byte) (n int, err error) {
33-
r.prevRune = -1
3433
if len(b) == 0 {
3534
return 0, nil
3635
}
3736
if r.i >= int64(len(r.s)) {
3837
return 0, io.EOF
3938
}
39+
r.prevRune = -1
4040
n = copy(b, r.s[r.i:])
4141
r.i += int64(n)
4242
return

src/pkg/bytes/reader_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ func TestReaderAtConcurrent(t *testing.T) {
115115
wg.Wait()
116116
}
117117

118+
func TestEmptyReaderConcurrent(t *testing.T) {
119+
// Test for the race detector, to verify a Read that doesn't yield any bytes
120+
// is okay to use from multiple goroutines. This was our historic behavior.
121+
// See golang.org/issue/7856
122+
r := NewReader([]byte{})
123+
var wg sync.WaitGroup
124+
for i := 0; i < 5; i++ {
125+
wg.Add(2)
126+
go func() {
127+
defer wg.Done()
128+
var buf [1]byte
129+
r.Read(buf[:])
130+
}()
131+
go func() {
132+
defer wg.Done()
133+
r.Read(nil)
134+
}()
135+
}
136+
wg.Wait()
137+
}
138+
118139
func TestReaderWriteTo(t *testing.T) {
119140
for i := 0; i < 30; i += 3 {
120141
var l int
@@ -164,7 +185,7 @@ var UnreadRuneErrorTests = []struct {
164185
name string
165186
f func(*Reader)
166187
}{
167-
{"Read", func(r *Reader) { r.Read([]byte{}) }},
188+
{"Read", func(r *Reader) { r.Read([]byte{0}) }},
168189
{"ReadByte", func(r *Reader) { r.ReadByte() }},
169190
{"UnreadRune", func(r *Reader) { r.UnreadRune() }},
170191
{"Seek", func(r *Reader) { r.Seek(0, 1) }},

src/pkg/net/http/serve_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2461,6 +2461,39 @@ func TestServerKeepAlivesEnabled(t *testing.T) {
24612461
}
24622462
}
24632463

2464+
// golang.org/issue/7856
2465+
func TestServerEmptyBodyRace(t *testing.T) {
2466+
defer afterTest(t)
2467+
var n int32
2468+
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
2469+
atomic.AddInt32(&n, 1)
2470+
}))
2471+
defer ts.Close()
2472+
var wg sync.WaitGroup
2473+
const reqs = 20
2474+
for i := 0; i < reqs; i++ {
2475+
wg.Add(1)
2476+
go func() {
2477+
defer wg.Done()
2478+
res, err := Get(ts.URL)
2479+
if err != nil {
2480+
t.Error(err)
2481+
return
2482+
}
2483+
defer res.Body.Close()
2484+
_, err = io.Copy(ioutil.Discard, res.Body)
2485+
if err != nil {
2486+
t.Error(err)
2487+
return
2488+
}
2489+
}()
2490+
}
2491+
wg.Wait()
2492+
if got := atomic.LoadInt32(&n); got != reqs {
2493+
t.Errorf("handler ran %d times; want %d", got, reqs)
2494+
}
2495+
}
2496+
24642497
func TestServerConnStateNew(t *testing.T) {
24652498
sawNew := false // if the test is buggy, we'll race on this variable.
24662499
srv := &Server{

src/pkg/net/http/server.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,17 +1971,24 @@ func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
19711971
}
19721972
}
19731973

1974+
type eofReaderWithWriteTo struct{}
1975+
1976+
func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil }
1977+
func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF }
1978+
19741979
// eofReader is a non-nil io.ReadCloser that always returns EOF.
1975-
// It embeds a *strings.Reader so it still has a WriteTo method
1976-
// and io.Copy won't need a buffer.
1980+
// It has a WriteTo method so io.Copy won't need a buffer.
19771981
var eofReader = &struct {
1978-
*strings.Reader
1982+
eofReaderWithWriteTo
19791983
io.Closer
19801984
}{
1981-
strings.NewReader(""),
1985+
eofReaderWithWriteTo{},
19821986
ioutil.NopCloser(nil),
19831987
}
19841988

1989+
// Verify that an io.Copy from an eofReader won't require a buffer.
1990+
var _ io.WriterTo = eofReader
1991+
19851992
// initNPNRequest is an HTTP handler that initializes certain
19861993
// uninitialized fields in its *Request. Such partially-initialized
19871994
// Requests come from NPN protocol handlers.

src/pkg/strings/reader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ func (r *Reader) Len() int {
2929
}
3030

3131
func (r *Reader) Read(b []byte) (n int, err error) {
32-
r.prevRune = -1
3332
if len(b) == 0 {
3433
return 0, nil
3534
}
3635
if r.i >= int64(len(r.s)) {
3736
return 0, io.EOF
3837
}
38+
r.prevRune = -1
3939
n = copy(b, r.s[r.i:])
4040
r.i += int64(n)
4141
return

src/pkg/strings/reader_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,27 @@ func TestReaderAtConcurrent(t *testing.T) {
115115
wg.Wait()
116116
}
117117

118+
func TestEmptyReaderConcurrent(t *testing.T) {
119+
// Test for the race detector, to verify a Read that doesn't yield any bytes
120+
// is okay to use from multiple goroutines. This was our historic behavior.
121+
// See golang.org/issue/7856
122+
r := strings.NewReader("")
123+
var wg sync.WaitGroup
124+
for i := 0; i < 5; i++ {
125+
wg.Add(2)
126+
go func() {
127+
defer wg.Done()
128+
var buf [1]byte
129+
r.Read(buf[:])
130+
}()
131+
go func() {
132+
defer wg.Done()
133+
r.Read(nil)
134+
}()
135+
}
136+
wg.Wait()
137+
}
138+
118139
func TestWriteTo(t *testing.T) {
119140
const str = "0123456789"
120141
for i := 0; i <= len(str); i++ {

src/pkg/strings/strings_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,7 +862,7 @@ var UnreadRuneErrorTests = []struct {
862862
name string
863863
f func(*Reader)
864864
}{
865-
{"Read", func(r *Reader) { r.Read([]byte{}) }},
865+
{"Read", func(r *Reader) { r.Read([]byte{0}) }},
866866
{"ReadByte", func(r *Reader) { r.ReadByte() }},
867867
{"UnreadRune", func(r *Reader) { r.UnreadRune() }},
868868
{"Seek", func(r *Reader) { r.Seek(0, 1) }},

0 commit comments

Comments
 (0)