transport: skip Status.Proto() without details in writeStatus (#8282)

This commit is contained in:
Evan Jones 2025-05-07 14:20:46 -04:00 committed by GitHub
parent 35aea9cd90
commit c7aec4defb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 55 additions and 3 deletions

View File

@ -236,3 +236,11 @@ func IsRestrictedControlPlaneCode(s *Status) bool {
}
return false
}
// RawStatusProto returns the internal protobuf message for use by gRPC itself.
func RawStatusProto(s *Status) *spb.Status {
if s == nil {
return nil
}
return s.s
}

View File

@ -0,0 +1,40 @@
/*
*
* Copyright 2025 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package status_test
import (
"testing"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/internal/status"
)
func TestRawStatusProto(t *testing.T) {
spb := status.RawStatusProto(nil)
if spb != nil {
t.Errorf("RawStatusProto(nil) = %v; must return nil", spb)
}
s := status.New(codes.Internal, "test internal error")
spb1 := status.RawStatusProto(s)
spb2 := status.RawStatusProto(s)
// spb1 and spb2 should be the same pointer: no copies
if spb1 != spb2 {
t.Errorf("RawStatusProto(s)=%p then %p; must return the same pointer", spb1, spb2)
}
}

View File

@ -39,6 +39,7 @@ import (
"google.golang.org/grpc/internal/grpclog"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/internal/pretty"
istatus "google.golang.org/grpc/internal/status"
"google.golang.org/grpc/internal/syscall"
"google.golang.org/grpc/mem"
"google.golang.org/protobuf/proto"
@ -1055,7 +1056,7 @@ func (t *http2Server) writeHeaderLocked(s *ServerStream) error {
return nil
}
// WriteStatus sends stream status to the client and terminates the stream.
// writeStatus sends stream status to the client and terminates the stream.
// There is no further I/O operations being able to perform on this stream.
// TODO(zhaoq): Now it indicates the end of entire stream. Revisit if early
// OK is adopted.
@ -1083,7 +1084,7 @@ func (t *http2Server) writeStatus(s *ServerStream, st *status.Status) error {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))})
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())})
if p := st.Proto(); p != nil && len(p.Details) > 0 {
if p := istatus.RawStatusProto(st); len(p.GetDetails()) > 0 {
// Do not use the user's grpc-status-details-bin (if present) if we are
// even attempting to set our own.
delete(s.trailer, grpcStatusDetailsBinHeader)

View File

@ -354,6 +354,9 @@ func (s) TestStatus_ErrorDetails(t *testing.T) {
t.Fatalf("(%v).WithDetails(%+v) failed: %v", str(s), tc.details, err)
}
details := s.Details()
if len(details) != len(tc.details) {
t.Fatalf("len(s.Details()) = %d, want = %d.", len(details), len(tc.details))
}
for i := range details {
if !proto.Equal(details[i].(protoreflect.ProtoMessage), tc.details[i].(protoreflect.ProtoMessage)) {
t.Fatalf("(%v).Details()[%d] = %+v, want %+v", str(s), i, details[i], tc.details[i])
@ -420,7 +423,7 @@ func (s) TestStatus_ErrorDetails_Fail(t *testing.T) {
for _, tc := range tests {
details := tc.s.Details()
if len(details) != len(tc.want) {
t.Fatalf("len(s.Details()) = %v, want = %v.", len(details), len(tc.want))
t.Fatalf("len(s.Details()) = %d, want = %d.", len(details), len(tc.want))
}
for i, d := range details {
// s.Details can either contain an error or a proto message. We