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
}
func (r *Reader) Read(b []byte) (n int, err error) {
- r.prevRune = -1
if len(b) == 0 {
return 0, nil
}
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
+ r.prevRune = -1
n = copy(b, r.s[r.i:])
r.i += int64(n)
return
wg.Wait()
}
+func TestEmptyReaderConcurrent(t *testing.T) {
+ // Test for the race detector, to verify a Read that doesn't yield any bytes
+ // is okay to use from multiple goroutines. This was our historic behavior.
+ // See golang.org/issue/7856
+ r := NewReader([]byte{})
+ var wg sync.WaitGroup
+ for i := 0; i < 5; i++ {
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ var buf [1]byte
+ r.Read(buf[:])
+ }()
+ go func() {
+ defer wg.Done()
+ r.Read(nil)
+ }()
+ }
+ wg.Wait()
+}
+
func TestReaderWriteTo(t *testing.T) {
for i := 0; i < 30; i += 3 {
var l int
name string
f func(*Reader)
}{
- {"Read", func(r *Reader) { r.Read([]byte{}) }},
+ {"Read", func(r *Reader) { r.Read([]byte{0}) }},
{"ReadByte", func(r *Reader) { r.ReadByte() }},
{"UnreadRune", func(r *Reader) { r.UnreadRune() }},
{"Seek", func(r *Reader) { r.Seek(0, 1) }},
}
}
+// golang.org/issue/7856
+func TestServerEmptyBodyRace(t *testing.T) {
+ defer afterTest(t)
+ var n int32
+ ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
+ atomic.AddInt32(&n, 1)
+ }))
+ defer ts.Close()
+ var wg sync.WaitGroup
+ const reqs = 20
+ for i := 0; i < reqs; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ res, err := Get(ts.URL)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer res.Body.Close()
+ _, err = io.Copy(ioutil.Discard, res.Body)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ }()
+ }
+ wg.Wait()
+ if got := atomic.LoadInt32(&n); got != reqs {
+ t.Errorf("handler ran %d times; want %d", got, reqs)
+ }
+}
+
func TestServerConnStateNew(t *testing.T) {
sawNew := false // if the test is buggy, we'll race on this variable.
srv := &Server{
}
}
+type eofReaderWithWriteTo struct{}
+
+func (eofReaderWithWriteTo) WriteTo(io.Writer) (int64, error) { return 0, nil }
+func (eofReaderWithWriteTo) Read([]byte) (int, error) { return 0, io.EOF }
+
// eofReader is a non-nil io.ReadCloser that always returns EOF.
-// It embeds a *strings.Reader so it still has a WriteTo method
-// and io.Copy won't need a buffer.
+// It has a WriteTo method so io.Copy won't need a buffer.
var eofReader = &struct {
- *strings.Reader
+ eofReaderWithWriteTo
io.Closer
}{
- strings.NewReader(""),
+ eofReaderWithWriteTo{},
ioutil.NopCloser(nil),
}
+// Verify that an io.Copy from an eofReader won't require a buffer.
+var _ io.WriterTo = eofReader
+
// initNPNRequest is an HTTP handler that initializes certain
// uninitialized fields in its *Request. Such partially-initialized
// Requests come from NPN protocol handlers.
}
func (r *Reader) Read(b []byte) (n int, err error) {
- r.prevRune = -1
if len(b) == 0 {
return 0, nil
}
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
+ r.prevRune = -1
n = copy(b, r.s[r.i:])
r.i += int64(n)
return
wg.Wait()
}
+func TestEmptyReaderConcurrent(t *testing.T) {
+ // Test for the race detector, to verify a Read that doesn't yield any bytes
+ // is okay to use from multiple goroutines. This was our historic behavior.
+ // See golang.org/issue/7856
+ r := strings.NewReader("")
+ var wg sync.WaitGroup
+ for i := 0; i < 5; i++ {
+ wg.Add(2)
+ go func() {
+ defer wg.Done()
+ var buf [1]byte
+ r.Read(buf[:])
+ }()
+ go func() {
+ defer wg.Done()
+ r.Read(nil)
+ }()
+ }
+ wg.Wait()
+}
+
func TestWriteTo(t *testing.T) {
const str = "0123456789"
for i := 0; i <= len(str); i++ {
name string
f func(*Reader)
}{
- {"Read", func(r *Reader) { r.Read([]byte{}) }},
+ {"Read", func(r *Reader) { r.Read([]byte{0}) }},
{"ReadByte", func(r *Reader) { r.ReadByte() }},
{"UnreadRune", func(r *Reader) { r.UnreadRune() }},
{"Seek", func(r *Reader) { r.Seek(0, 1) }},