-
Notifications
You must be signed in to change notification settings - Fork 4.6k
transport: ensure header mutex is held while copying trailers in handler_server #8519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ import ( | |
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/mem" | ||
"google.golang.org/grpc/metadata" | ||
"google.golang.org/grpc/stats" | ||
"google.golang.org/grpc/status" | ||
"google.golang.org/protobuf/proto" | ||
"google.golang.org/protobuf/protoadapt" | ||
|
@@ -246,6 +247,22 @@ type handleStreamTest struct { | |
ht *serverHandlerTransport | ||
} | ||
|
||
type mockStatsHandler struct{} | ||
|
||
func (h *mockStatsHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { | ||
return ctx | ||
} | ||
|
||
func (h *mockStatsHandler) HandleRPC(context.Context, stats.RPCStats) { | ||
} | ||
|
||
func (h *mockStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { | ||
return ctx | ||
} | ||
|
||
func (h *mockStatsHandler) HandleConn(context.Context, stats.ConnStats) { | ||
} | ||
|
||
func newHandleStreamTest(t *testing.T) *handleStreamTest { | ||
bodyr, bodyw := io.Pipe() | ||
req := &http.Request{ | ||
|
@@ -260,7 +277,12 @@ func newHandleStreamTest(t *testing.T) *handleStreamTest { | |
Body: bodyr, | ||
} | ||
rw := newTestHandlerResponseWriter().(testHandlerResponseWriter) | ||
ht, err := NewServerHandlerTransport(rw, req, nil, mem.DefaultBufferPool()) | ||
// Add mock stats handlers to exercise the stats handler code path. | ||
statsHandlers := make([]stats.Handler, 0, 5) | ||
for range 5 { | ||
statsHandlers = append(statsHandlers, &mockStatsHandler{}) | ||
} | ||
ht, err := NewServerHandlerTransport(rw, req, statsHandlers, mem.DefaultBufferPool()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we parameterize this, or find some other way to cause this configuration, instead of doing it for all existing tests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
@@ -485,6 +507,12 @@ func (s) TestHandlerTransport_HandleStreams_ErrDetails(t *testing.T) { | |
|
||
hst := newHandleStreamTest(t) | ||
handleStream := func(s *ServerStream) { | ||
if err := s.SendHeader(metadata.New(map[string]string{})); err != nil { | ||
t.Error(err) | ||
} | ||
if err := s.SetTrailer(metadata.Pairs("custom-trailer", "Custom trailer value")); err != nil { | ||
t.Error(err) | ||
} | ||
Comment on lines
+563
to
+565
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How hard would it be to test this in a new test instead of in an existing test that's intended for testing error details? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to use a new test. My thought process was that modifying existing tests would give us better coverage for interactions b/w different features. |
||
s.WriteStatus(st) | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
|
@@ -501,6 +529,7 @@ func (s) TestHandlerTransport_HandleStreams_ErrDetails(t *testing.T) { | |
"Grpc-Status": {fmt.Sprint(uint32(statusCode))}, | ||
"Grpc-Message": {encodeGrpcMessage(msg)}, | ||
"Grpc-Status-Details-Bin": {encodeBinHeader(stBytes)}, | ||
"Custom-Trailer": []string{"Custom trailer value"}, | ||
} | ||
|
||
checkHeaderAndTrailer(t, hst.rw, wantHeader, wantTrailer) | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're holding two locks here, this and
ht.writeStatusMu
(acquired at line 229).ht.writeStatusMu
is only referenced in this method, so there shouldn't be a chance of deadlocks.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that
hdrMu
is also already taken on 249, although, I'm not sure if that closure is run in the current goroutine or another one.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The callback is executed in an event loop in a separate goroutine:
grpc-go/internal/transport/handler_server.go
Lines 465 to 474 in 9ac0ec8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about calling the stats handler in the callback above, but I noticed that the http2_server transport also schedules the network write in the background and calls the stats handlers.
grpc-go/internal/transport/http2_server.go
Lines 1136 to 1143 in 9ac0ec8