Skip to content

Commit

Permalink
http2: fix Server race with concurrent Read/Close
Browse files Browse the repository at this point in the history
Introduced by golang.org/cl/31447.

grpc-go does concurrent Read/Close calls to the Request.Body. Since
that was supported previously, continue to support it, and add a test.

Updates grpc/grpc-go#938

Change-Id: I8a5cf6b28c5eca346ea46c4129021172e489f71d
Reviewed-on: https://go-review.googlesource.com/31636
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
  • Loading branch information
bradfitz authored and ianlancetaylor committed Oct 21, 2016
1 parent daba796 commit 40a0a18
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 4 deletions.
1 change: 1 addition & 0 deletions http2/http2.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var (
VerboseLogs bool
logFrameWrites bool
logFrameReads bool
inTests bool
)

func init() {
Expand Down
1 change: 1 addition & 0 deletions http2/http2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func condSkipFailingTest(t *testing.T) {
}

func init() {
inTests = true
DebugGoroutines = true
flag.BoolVar(&VerboseLogs, "verboseh2", VerboseLogs, "Verbose HTTP/2 debug logging")
}
Expand Down
14 changes: 10 additions & 4 deletions http2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1858,16 +1858,19 @@ func (sc *serverConn) sendWindowUpdate32(st *stream, n int32) {
}
}

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

func (b *requestBody) Close() error {
if b.pipe != nil {
if b.pipe != nil && !b.closed {
b.pipe.BreakWithError(errClosedBody)
}
b.closed = true
Expand All @@ -1879,12 +1882,15 @@ func (b *requestBody) Read(p []byte) (n int, err error) {
b.needsContinue = false
b.conn.write100ContinueHeaders(b.stream)
}
if b.pipe == nil {
if b.pipe == nil || b.sawEOF {
return 0, io.EOF
}
n, err = b.pipe.Read(p)
if err == io.EOF {
b.pipe = nil
b.sawEOF = true
}
if b.conn == nil && inTests {
return
}
b.conn.noteBodyReadFromHandler(b.stream, n, err)
return
Expand Down
24 changes: 24 additions & 0 deletions http2/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3372,3 +3372,27 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) {
}

}

// grpc-go closes the Request.Body currently with a Read.
// Verify that it doesn't race.
// See https://github.com/grpc/grpc-go/pull/938
func TestRequestBodyReadCloseRace(t *testing.T) {
for i := 0; i < 100; i++ {
body := &requestBody{
pipe: &pipe{
b: new(bytes.Buffer),
},
}
body.pipe.CloseWithError(io.EOF)

done := make(chan bool, 1)
buf := make([]byte, 10)
go func() {
time.Sleep(1 * time.Millisecond)
body.Close()
done <- true
}()
body.Read(buf)
<-done
}
}

4 comments on commit 40a0a18

@voutasaurus
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradfitz it appears that two concurrent calls to Close() can still race here because b.closed is being checked and set in two separate operations (i.e. a check, check, set, set ordering is possible). Is this commit intended to support concurrent calls to Close()?

@nathany
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@voutasaurus Probably best to open a new issue that references this commit sha.

https://github.com/golang/go/issues/new
40a0a18

(otherwise your comment might get lost)

@bradfitz
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Is there code in the wild trying to concurrently close?

@bradfitz
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, what @nathany said. We don't use comments on github issues for anything.

Please sign in to comment.