From 652c14643cabce486c168a5af96175a354f32862 Mon Sep 17 00:00:00 2001 From: RPRX <63339210+RPRX@users.noreply.github.com> Date: Sat, 28 Dec 2024 02:52:17 +0000 Subject: [PATCH] http3: allow concurrent calls to Body.Close (#4798) --- http3/body.go | 13 ++++++------- http3/body_test.go | 12 ++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/http3/body.go b/http3/body.go index e52e9f79d9e..a49894ce6ce 100644 --- a/http3/body.go +++ b/http3/body.go @@ -4,6 +4,7 @@ import ( "context" "errors" "io" + "sync" "github.com/xtls/quic-go" ) @@ -95,8 +96,8 @@ type hijackableBody struct { // only set for the http.Response // The channel is closed when the user is done with this response: // either when Read() errors, or when Close() is called. - reqDone chan<- struct{} - reqDoneClosed bool + reqDone chan<- struct{} + reqDoneOnce sync.Once } var _ io.ReadCloser = &hijackableBody{} @@ -117,13 +118,11 @@ func (r *hijackableBody) Read(b []byte) (int, error) { } func (r *hijackableBody) requestDone() { - if r.reqDoneClosed || r.reqDone == nil { - return - } if r.reqDone != nil { - close(r.reqDone) + r.reqDoneOnce.Do(func() { + close(r.reqDone) + }) } - r.reqDoneClosed = true } func (r *hijackableBody) Close() error { diff --git a/http3/body_test.go b/http3/body_test.go index 1c3f5b30c1b..bb5a531edd8 100644 --- a/http3/body_test.go +++ b/http3/body_test.go @@ -54,6 +54,18 @@ var _ = Describe("Response Body", func() { Expect(rb.Close()).To(Succeed()) }) + It("allows concurrent calls to Close", func() { + str := mockquic.NewMockStream(mockCtrl) + rb := newResponseBody(&stream{Stream: str}, -1, reqDone) + str.EXPECT().CancelRead(quic.StreamErrorCode(ErrCodeRequestCanceled)).MaxTimes(2) + go func() { + defer GinkgoRecover() + Expect(rb.Close()).To(Succeed()) + }() + Expect(rb.Close()).To(Succeed()) + Expect(reqDone).To(BeClosed()) + }) + Context("length limiting", func() { It("reads all frames", func() { var buf bytes.Buffer