stats: add Trailer to client-side stats.End (#2639)

Currently, it is not possible to access trailers from within a
stats.Handler. The reason is that both stats.Handler and
ClientStream.Trailer require a lock on the ClientStream.

A workaround would be to start a separate goroutine that will call
ClientStream.Trailer asynchronously, but that requires careful
coordination and we can quite easily make the trailer metadata available
to the stats.Handler directly.

Use case: an interceptor that processes trailer metadata for each
streaming RPC after the stream has finished. Note that a
StreamClientInterceptor returns immediately, before the stream has
finished and before the trailer metadata is available.
This commit is contained in:
Marten Klencke 2019-03-13 18:10:52 +01:00 committed by Doug Fawley
parent d3f95b277a
commit 9c3a959569
3 changed files with 18 additions and 2 deletions

View File

@ -27,6 +27,8 @@ import (
"context"
"net"
"time"
"google.golang.org/grpc/metadata"
)
// RPCStats contains stats information about RPCs.
@ -172,6 +174,9 @@ type End struct {
BeginTime time.Time
// EndTime is the time when the RPC ends.
EndTime time.Time
// Trailer contains the trailer metadata received from the server. This
// field is only valid if this End is from the client side.
Trailer metadata.MD
// Error is the error the RPC ended with. It is an error generated from
// status.Status and can be converted back to status.Status using
// status.FromError if non-nil.

View File

@ -652,6 +652,16 @@ func checkEnd(t *testing.T, d *gotData, e *expectedData) {
if actual.Code() != expectedStatus.Code() || actual.Message() != expectedStatus.Message() {
t.Fatalf("st.Error = %v, want %v", st.Error, e.err)
}
if st.Client {
if !reflect.DeepEqual(st.Trailer, testTrailerMetadata) {
t.Fatalf("st.Trailer = %v, want %v", st.Trailer, testTrailerMetadata)
}
} else {
if st.Trailer != nil {
t.Fatalf("st.Trailer = %v, want nil", st.Trailer)
}
}
}
func checkConnBegin(t *testing.T, d *gotData, e *expectedData) {

View File

@ -915,16 +915,16 @@ func (a *csAttempt) finish(err error) {
// Ending a stream with EOF indicates a success.
err = nil
}
var tr metadata.MD
if a.s != nil {
a.t.CloseStream(a.s, err)
tr = a.s.Trailer()
}
if a.done != nil {
br := false
var tr metadata.MD
if a.s != nil {
br = a.s.BytesReceived()
tr = a.s.Trailer()
}
a.done(balancer.DoneInfo{
Err: err,
@ -938,6 +938,7 @@ func (a *csAttempt) finish(err error) {
Client: true,
BeginTime: a.cs.beginTime,
EndTime: time.Now(),
Trailer: tr,
Error: err,
}
a.statsHandler.HandleRPC(a.cs.ctx, end)