-
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
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #8519 +/- ##
==========================================
+ Coverage 81.87% 82.00% +0.12%
==========================================
Files 413 413
Lines 40518 40520 +2
==========================================
+ Hits 33176 33229 +53
+ Misses 5967 5908 -59
- Partials 1375 1383 +8
🚀 New features to boost your workflow:
|
@@ -277,11 +277,13 @@ func (ht *serverHandlerTransport) writeStatus(s *ServerStream, st *status.Status | |||
if err == nil { // transport has not been closed | |||
// Note: The trailer fields are compressed with hpack after this call returns. | |||
// No WireLength field is set here. | |||
s.hdrMu.Lock() |
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
func (ht *serverHandlerTransport) runStream() { | |
for { | |
select { | |
case fn := <-ht.writes: | |
fn() | |
case <-ht.closedCh: | |
return | |
} | |
} | |
} |
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
t.finishStream(s, rst, http2.ErrCodeNo, trailingHeader, true) | |
for _, sh := range t.stats { | |
// Note: The trailer fields are compressed with hpack after this call returns. | |
// No WireLength field is set here. | |
sh.HandleRPC(s.Context(), &stats.OutTrailer{ | |
Trailer: s.trailer.Copy(), | |
}) | |
} |
294df40
to
2fe9a4f
Compare
@@ -277,11 +277,13 @@ func (ht *serverHandlerTransport) writeStatus(s *ServerStream, st *status.Status | |||
if err == nil { // transport has not been closed | |||
// Note: The trailer fields are compressed with hpack after this call returns. | |||
// No WireLength field is set here. | |||
s.hdrMu.Lock() |
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.
if err := s.SetTrailer(metadata.Pairs("custom-trailer", "Custom trailer value")); err != nil { | ||
t.Error(err) | ||
} |
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.
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 comment
The 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.
// 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
55455d7
to
31a6f26
Compare
…ler_server (grpc#8519) Fixes: grpc#8514 The mutex that guards the trailers should be held while copying the trailers. We do lock the mutex in [the regular gRPC server transport](https://github.com/grpc/grpc-go/blob/9ac0ec87ca2ecc66b3c0c084708aef768637aef6/internal/transport/http2_server.go#L1140-L1142), but have missed it in the std lib http/2 transport. The only place where a write happens is `writeStatus()` is when the status contains a proto. https://github.com/grpc/grpc-go/blob/4375c784450aa7e43ff15b8b2879c896d0917130/internal/transport/handler_server.go#L251-L252 RELEASE NOTES: * transport: Fix a data race while copying headers for stats handlers in the std lib http2 server transport.
…ler_server (grpc#8519) Fixes: grpc#8514 The mutex that guards the trailers should be held while copying the trailers. We do lock the mutex in [the regular gRPC server transport](https://github.com/grpc/grpc-go/blob/9ac0ec87ca2ecc66b3c0c084708aef768637aef6/internal/transport/http2_server.go#L1140-L1142), but have missed it in the std lib http/2 transport. The only place where a write happens is `writeStatus()` is when the status contains a proto. https://github.com/grpc/grpc-go/blob/4375c784450aa7e43ff15b8b2879c896d0917130/internal/transport/handler_server.go#L251-L252 RELEASE NOTES: * transport: Fix a data race while copying headers for stats handlers in the std lib http2 server transport.
…ler_server (grpc#8519) Fixes: grpc#8514 The mutex that guards the trailers should be held while copying the trailers. We do lock the mutex in [the regular gRPC server transport](https://github.com/grpc/grpc-go/blob/9ac0ec87ca2ecc66b3c0c084708aef768637aef6/internal/transport/http2_server.go#L1140-L1142), but have missed it in the std lib http/2 transport. The only place where a write happens is `writeStatus()` is when the status contains a proto. https://github.com/grpc/grpc-go/blob/4375c784450aa7e43ff15b8b2879c896d0917130/internal/transport/handler_server.go#L251-L252 RELEASE NOTES: * transport: Fix a data race while copying headers for stats handlers in the std lib http2 server transport.
Fixes: #8514
The mutex that guards the trailers should be held while copying the trailers. We do lock the mutex in the regular gRPC server transport, but have missed it in the std lib http/2 transport. The only place where a write happens is
writeStatus()
is when the status contains a proto.grpc-go/internal/transport/handler_server.go
Lines 251 to 252 in 4375c78
RELEASE NOTES: