chore: deps and vendor

This commit is contained in:
2024-12-27 13:47:45 +01:00
parent 671e1ca276
commit 18df498295
245 changed files with 9652 additions and 3484 deletions

View File

@ -159,14 +159,14 @@ var file_google_api_httpbody_proto_rawDesc = []byte{
0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x34, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52,
0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x68, 0x0a, 0x0e, 0x63,
0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x65, 0x0a, 0x0e, 0x63,
0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0d, 0x48,
0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3b,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72,
0x67, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f,
0x64, 0x79, 0x3b, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f, 0x64, 0x79, 0xf8, 0x01, 0x01, 0xa2, 0x02,
0x04, 0x47, 0x41, 0x50, 0x49, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x64, 0x79, 0x3b, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f, 0x64, 0x79, 0xa2, 0x02, 0x04, 0x47, 0x41,
0x50, 0x49, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@ -80,11 +80,12 @@ type ErrorInfo struct {
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
// Additional structured details about this error.
//
// Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in
// Keys must match a regular expression of `[a-z][a-zA-Z0-9-_]+` but should
// ideally be lowerCamelCase. Also, they must be limited to 64 characters in
// length. When identifying the current value of an exceeded limit, the units
// should be contained in the key, not the value. For example, rather than
// {"instanceLimit": "100/request"}, should be returned as,
// {"instanceLimitPerRequest": "100"}, if the client exceeds the number of
// `{"instanceLimit": "100/request"}`, should be returned as,
// `{"instanceLimitPerRequest": "100"}`, if the client exceeds the number of
// instances that can be created in a single (batch) request.
Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
@ -870,6 +871,16 @@ type BadRequest_FieldViolation struct {
Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"`
// A description of why the request element is bad.
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
// The reason of the field-level error. This is a constant value that
// identifies the proximate cause of the field-level error. It should
// uniquely identify the type of the FieldViolation within the scope of the
// google.rpc.ErrorInfo.domain. This should be at most 63
// characters and match a regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`,
// which represents UPPER_SNAKE_CASE.
Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"`
// Provides a localized error message for field-level errors that is safe to
// return to the API consumer.
LocalizedMessage *LocalizedMessage `protobuf:"bytes,4,opt,name=localized_message,json=localizedMessage,proto3" json:"localized_message,omitempty"`
}
func (x *BadRequest_FieldViolation) Reset() {
@ -918,6 +929,20 @@ func (x *BadRequest_FieldViolation) GetDescription() string {
return ""
}
func (x *BadRequest_FieldViolation) GetReason() string {
if x != nil {
return x.Reason
}
return ""
}
func (x *BadRequest_FieldViolation) GetLocalizedMessage() *LocalizedMessage {
if x != nil {
return x.LocalizedMessage
}
return nil
}
// Describes a URL link.
type Help_Link struct {
state protoimpl.MessageState
@ -1026,51 +1051,57 @@ var file_google_rpc_error_details_proto_rawDesc = []byte{
0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xa8, 0x01, 0x0a, 0x0a, 0x42, 0x61,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x8c, 0x02, 0x0a, 0x0a, 0x42, 0x61,
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x50, 0x0a, 0x10, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x5f, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e,
0x42, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64,
0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64,
0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x48, 0x0a, 0x0e, 0x46, 0x69,
0x65, 0x6c, 0x64, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05,
0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65,
0x6c, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4f, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49,
0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61,
0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e,
0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0x90, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x04, 0x48, 0x65, 0x6c, 0x70,
0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x65, 0x6c,
0x70, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x1a, 0x3a, 0x0a,
0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x44, 0x0a, 0x10, 0x4c, 0x6f, 0x63,
0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c,
0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42,
0x6c, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70,
0x63, 0x42, 0x11, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x50,
0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67,
0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x72, 0x70,
0x63, 0x2f, 0x65, 0x72, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x3b, 0x65, 0x72, 0x72,
0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0xa2, 0x02, 0x03, 0x52, 0x50, 0x43, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0xab, 0x01, 0x0a, 0x0e, 0x46,
0x69, 0x65, 0x6c, 0x64, 0x56, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a,
0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69,
0x65, 0x6c, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18,
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x49, 0x0a,
0x11, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61,
0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d,
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x10, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65,
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x4f, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e,
0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65,
0x72, 0x76, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0x90, 0x01, 0x0a, 0x0c, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12,
0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x04,
0x48, 0x65, 0x6c, 0x70, 0x12, 0x2b, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x72, 0x70, 0x63,
0x2e, 0x48, 0x65, 0x6c, 0x70, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x6b,
0x73, 0x1a, 0x3a, 0x0a, 0x04, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73,
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x75,
0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x44, 0x0a,
0x10, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73,
0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73,
0x61, 0x67, 0x65, 0x42, 0x6c, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2e, 0x72, 0x70, 0x63, 0x42, 0x11, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61,
0x69, 0x6c, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3f, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x65,
0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69,
0x73, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x72, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
0x3b, 0x65, 0x72, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0xa2, 0x02, 0x03, 0x52, 0x50,
0x43, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1111,11 +1142,12 @@ var file_google_rpc_error_details_proto_depIdxs = []int32{
12, // 3: google.rpc.PreconditionFailure.violations:type_name -> google.rpc.PreconditionFailure.Violation
13, // 4: google.rpc.BadRequest.field_violations:type_name -> google.rpc.BadRequest.FieldViolation
14, // 5: google.rpc.Help.links:type_name -> google.rpc.Help.Link
6, // [6:6] is the sub-list for method output_type
6, // [6:6] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
9, // 6: google.rpc.BadRequest.FieldViolation.localized_message:type_name -> google.rpc.LocalizedMessage
7, // [7:7] is the sub-list for method output_type
7, // [7:7] is the sub-list for method input_type
7, // [7:7] is the sub-list for extension type_name
7, // [7:7] is the sub-list for extension extendee
0, // [0:7] is the sub-list for field type_name
}
func init() { file_google_rpc_error_details_proto_init() }

View File

@ -73,17 +73,6 @@ func unregisterForTesting(name string) {
delete(m, name)
}
// connectedAddress returns the connected address for a SubConnState. The
// address is only valid if the state is READY.
func connectedAddress(scs SubConnState) resolver.Address {
return scs.connectedAddress
}
// setConnectedAddress sets the connected address for a SubConnState.
func setConnectedAddress(scs *SubConnState, addr resolver.Address) {
scs.connectedAddress = addr
}
func init() {
internal.BalancerUnregister = unregisterForTesting
internal.ConnectedAddress = connectedAddress
@ -106,57 +95,6 @@ func Get(name string) Builder {
return nil
}
// A SubConn represents a single connection to a gRPC backend service.
//
// Each SubConn contains a list of addresses.
//
// All SubConns start in IDLE, and will not try to connect. To trigger the
// connecting, Balancers must call Connect. If a connection re-enters IDLE,
// Balancers must call Connect again to trigger a new connection attempt.
//
// gRPC will try to connect to the addresses in sequence, and stop trying the
// remainder once the first connection is successful. If an attempt to connect
// to all addresses encounters an error, the SubConn will enter
// TRANSIENT_FAILURE for a backoff period, and then transition to IDLE.
//
// Once established, if a connection is lost, the SubConn will transition
// directly to IDLE.
//
// This interface is to be implemented by gRPC. Users should not need their own
// implementation of this interface. For situations like testing, any
// implementations should embed this interface. This allows gRPC to add new
// methods to this interface.
type SubConn interface {
// UpdateAddresses updates the addresses used in this SubConn.
// gRPC checks if currently-connected address is still in the new list.
// If it's in the list, the connection will be kept.
// If it's not in the list, the connection will gracefully close, and
// a new connection will be created.
//
// This will trigger a state transition for the SubConn.
//
// Deprecated: this method will be removed. Create new SubConns for new
// addresses instead.
UpdateAddresses([]resolver.Address)
// Connect starts the connecting for this SubConn.
Connect()
// GetOrBuildProducer returns a reference to the existing Producer for this
// ProducerBuilder in this SubConn, or, if one does not currently exist,
// creates a new one and returns it. Returns a close function which may be
// called when the Producer is no longer needed. Otherwise the producer
// will automatically be closed upon connection loss or subchannel close.
// Should only be called on a SubConn in state Ready. Otherwise the
// producer will be unable to create streams.
GetOrBuildProducer(ProducerBuilder) (p Producer, close func())
// Shutdown shuts down the SubConn gracefully. Any started RPCs will be
// allowed to complete. No future calls should be made on the SubConn.
// One final state update will be delivered to the StateListener (or
// UpdateSubConnState; deprecated) with ConnectivityState of Shutdown to
// indicate the shutdown operation. This may be delivered before
// in-progress RPCs are complete and the actual connection is closed.
Shutdown()
}
// NewSubConnOptions contains options to create new SubConn.
type NewSubConnOptions struct {
// CredsBundle is the credentials bundle that will be used in the created
@ -424,18 +362,6 @@ type ExitIdler interface {
ExitIdle()
}
// SubConnState describes the state of a SubConn.
type SubConnState struct {
// ConnectivityState is the connectivity state of the SubConn.
ConnectivityState connectivity.State
// ConnectionError is set if the ConnectivityState is TransientFailure,
// describing the reason the SubConn failed. Otherwise, it is nil.
ConnectionError error
// connectedAddr contains the connected address when ConnectivityState is
// Ready. Otherwise, it is indeterminate.
connectedAddress resolver.Address
}
// ClientConnState describes the state of a ClientConn relevant to the
// balancer.
type ClientConnState struct {
@ -448,22 +374,3 @@ type ClientConnState struct {
// ErrBadResolverState may be returned by UpdateClientConnState to indicate a
// problem with the provided name resolver data.
var ErrBadResolverState = errors.New("bad resolver state")
// A ProducerBuilder is a simple constructor for a Producer. It is used by the
// SubConn to create producers when needed.
type ProducerBuilder interface {
// Build creates a Producer. The first parameter is always a
// grpc.ClientConnInterface (a type to allow creating RPCs/streams on the
// associated SubConn), but is declared as `any` to avoid a dependency
// cycle. Build also returns a close function that will be called when all
// references to the Producer have been given up for a SubConn, or when a
// connectivity state change occurs on the SubConn. The close function
// should always block until all asynchronous cleanup work is completed.
Build(grpcClientConnInterface any) (p Producer, close func())
}
// A Producer is a type shared among potentially many consumers. It is
// associated with a SubConn, and an implementation will typically contain
// other methods to provide additional functionality, e.g. configuration or
// subscription registration.
type Producer any

View File

@ -18,7 +18,18 @@
// Package internal contains code internal to the pickfirst package.
package internal
import "math/rand"
import (
rand "math/rand/v2"
"time"
)
// RandShuffle pseudo-randomizes the order of addresses.
var RandShuffle = rand.Shuffle
var (
// RandShuffle pseudo-randomizes the order of addresses.
RandShuffle = rand.Shuffle
// TimeAfterFunc allows mocking the timer for testing connection delay
// related functionality.
TimeAfterFunc = func(d time.Duration, f func()) func() {
timer := time.AfterFunc(d, f)
return func() { timer.Stop() }
}
)

View File

@ -23,7 +23,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math/rand"
rand "math/rand/v2"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/pickfirst/internal"

View File

@ -29,11 +29,15 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/netip"
"sync"
"time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/pickfirst/internal"
"google.golang.org/grpc/connectivity"
expstats "google.golang.org/grpc/experimental/stats"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/envconfig"
internalgrpclog "google.golang.org/grpc/internal/grpclog"
@ -50,26 +54,68 @@ func init() {
balancer.Register(pickfirstBuilder{})
}
// enableHealthListenerKeyType is a unique key type used in resolver attributes
// to indicate whether the health listener usage is enabled.
type enableHealthListenerKeyType struct{}
var (
logger = grpclog.Component("pick-first-leaf-lb")
// Name is the name of the pick_first_leaf balancer.
// It is changed to "pick_first" in init() if this balancer is to be
// registered as the default pickfirst.
Name = "pick_first_leaf"
Name = "pick_first_leaf"
disconnectionsMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{
Name: "grpc.lb.pick_first.disconnections",
Description: "EXPERIMENTAL. Number of times the selected subchannel becomes disconnected.",
Unit: "disconnection",
Labels: []string{"grpc.target"},
Default: false,
})
connectionAttemptsSucceededMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{
Name: "grpc.lb.pick_first.connection_attempts_succeeded",
Description: "EXPERIMENTAL. Number of successful connection attempts.",
Unit: "attempt",
Labels: []string{"grpc.target"},
Default: false,
})
connectionAttemptsFailedMetric = expstats.RegisterInt64Count(expstats.MetricDescriptor{
Name: "grpc.lb.pick_first.connection_attempts_failed",
Description: "EXPERIMENTAL. Number of failed connection attempts.",
Unit: "attempt",
Labels: []string{"grpc.target"},
Default: false,
})
)
// TODO: change to pick-first when this becomes the default pick_first policy.
const logPrefix = "[pick-first-leaf-lb %p] "
const (
// TODO: change to pick-first when this becomes the default pick_first policy.
logPrefix = "[pick-first-leaf-lb %p] "
// connectionDelayInterval is the time to wait for during the happy eyeballs
// pass before starting the next connection attempt.
connectionDelayInterval = 250 * time.Millisecond
)
type ipAddrFamily int
const (
// ipAddrFamilyUnknown represents strings that can't be parsed as an IP
// address.
ipAddrFamilyUnknown ipAddrFamily = iota
ipAddrFamilyV4
ipAddrFamilyV6
)
type pickfirstBuilder struct{}
func (pickfirstBuilder) Build(cc balancer.ClientConn, _ balancer.BuildOptions) balancer.Balancer {
func (pickfirstBuilder) Build(cc balancer.ClientConn, bo balancer.BuildOptions) balancer.Balancer {
b := &pickfirstBalancer{
cc: cc,
addressList: addressList{},
subConns: resolver.NewAddressMap(),
state: connectivity.Connecting,
mu: sync.Mutex{},
cc: cc,
target: bo.Target.String(),
metricsRecorder: bo.MetricsRecorder, // ClientConn will always create a Metrics Recorder.
subConns: resolver.NewAddressMap(),
state: connectivity.Connecting,
cancelConnectionTimer: func() {},
}
b.logger = internalgrpclog.NewPrefixLogger(logger, fmt.Sprintf(logPrefix, b))
return b
@ -87,6 +133,13 @@ func (pickfirstBuilder) ParseConfig(js json.RawMessage) (serviceconfig.LoadBalan
return cfg, nil
}
// EnableHealthListener updates the state to configure pickfirst for using a
// generic health listener.
func EnableHealthListener(state resolver.State) resolver.State {
state.Attributes = state.Attributes.WithValue(enableHealthListenerKeyType{}, true)
return state
}
type pfConfig struct {
serviceconfig.LoadBalancingConfig `json:"-"`
@ -104,14 +157,19 @@ type scData struct {
subConn balancer.SubConn
addr resolver.Address
state connectivity.State
lastErr error
rawConnectivityState connectivity.State
// The effective connectivity state based on raw connectivity, health state
// and after following sticky TransientFailure behaviour defined in A62.
effectiveState connectivity.State
lastErr error
connectionFailedInFirstPass bool
}
func (b *pickfirstBalancer) newSCData(addr resolver.Address) (*scData, error) {
sd := &scData{
state: connectivity.Idle,
addr: addr,
rawConnectivityState: connectivity.Idle,
effectiveState: connectivity.Idle,
addr: addr,
}
sc, err := b.cc.NewSubConn([]resolver.Address{addr}, balancer.NewSubConnOptions{
StateListener: func(state balancer.SubConnState) {
@ -128,19 +186,25 @@ func (b *pickfirstBalancer) newSCData(addr resolver.Address) (*scData, error) {
type pickfirstBalancer struct {
// The following fields are initialized at build time and read-only after
// that and therefore do not need to be guarded by a mutex.
logger *internalgrpclog.PrefixLogger
cc balancer.ClientConn
logger *internalgrpclog.PrefixLogger
cc balancer.ClientConn
target string
metricsRecorder expstats.MetricsRecorder // guaranteed to be non nil
// The mutex is used to ensure synchronization of updates triggered
// from the idle picker and the already serialized resolver,
// SubConn state updates.
mu sync.Mutex
mu sync.Mutex
// State reported to the channel based on SubConn states and resolver
// updates.
state connectivity.State
// scData for active subonns mapped by address.
subConns *resolver.AddressMap
addressList addressList
firstPass bool
numTF int
subConns *resolver.AddressMap
addressList addressList
firstPass bool
numTF int
cancelConnectionTimer func()
healthCheckingEnabled bool
}
// ResolverError is called by the ClientConn when the name resolver produces
@ -166,7 +230,7 @@ func (b *pickfirstBalancer) resolverErrorLocked(err error) {
return
}
b.cc.UpdateState(balancer.State{
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: &picker{err: fmt.Errorf("name resolver error: %v", err)},
})
@ -175,15 +239,16 @@ func (b *pickfirstBalancer) resolverErrorLocked(err error) {
func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
b.mu.Lock()
defer b.mu.Unlock()
b.cancelConnectionTimer()
if len(state.ResolverState.Addresses) == 0 && len(state.ResolverState.Endpoints) == 0 {
// Cleanup state pertaining to the previous resolver state.
// Treat an empty address list like an error by calling b.ResolverError.
b.state = connectivity.TransientFailure
b.closeSubConnsLocked()
b.addressList.updateAddrs(nil)
b.resolverErrorLocked(errors.New("produced zero addresses"))
return balancer.ErrBadResolverState
}
b.healthCheckingEnabled = state.ResolverState.Attributes.Value(enableHealthListenerKeyType{}) != nil
cfg, ok := state.BalancerConfig.(pfConfig)
if state.BalancerConfig != nil && !ok {
return fmt.Errorf("pickfirst: received illegal BalancerConfig (type %T): %v: %w", state.BalancerConfig, state.BalancerConfig, balancer.ErrBadResolverState)
@ -206,9 +271,6 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
// "Flatten the list by concatenating the ordered list of addresses for
// each of the endpoints, in order." - A61
for _, endpoint := range endpoints {
// "In the flattened list, interleave addresses from the two address
// families, as per RFC-8305 section 4." - A61
// TODO: support the above language.
newAddrs = append(newAddrs, endpoint.Addresses...)
}
} else {
@ -231,16 +293,17 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
// Not de-duplicating would result in attempting to connect to the same
// SubConn multiple times in the same pass. We don't want this.
newAddrs = deDupAddresses(newAddrs)
newAddrs = interleaveAddresses(newAddrs)
// Since we have a new set of addresses, we are again at first pass.
b.firstPass = true
prevAddr := b.addressList.currentAddress()
prevSCData, found := b.subConns.Get(prevAddr)
prevAddrsCount := b.addressList.size()
isPrevRawConnectivityStateReady := found && prevSCData.(*scData).rawConnectivityState == connectivity.Ready
b.addressList.updateAddrs(newAddrs)
// If the previous ready SubConn exists in new address list,
// keep this connection and don't create new SubConns.
prevAddr := b.addressList.currentAddress()
prevAddrsCount := b.addressList.size()
b.addressList.updateAddrs(newAddrs)
if b.state == connectivity.Ready && b.addressList.seekTo(prevAddr) {
if isPrevRawConnectivityStateReady && b.addressList.seekTo(prevAddr) {
return nil
}
@ -252,18 +315,17 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
// we should still enter CONNECTING because the sticky TF behaviour
// mentioned in A62 applies only when the TRANSIENT_FAILURE is reported
// due to connectivity failures.
if b.state == connectivity.Ready || b.state == connectivity.Connecting || prevAddrsCount == 0 {
if isPrevRawConnectivityStateReady || b.state == connectivity.Connecting || prevAddrsCount == 0 {
// Start connection attempt at first address.
b.state = connectivity.Connecting
b.cc.UpdateState(balancer.State{
b.forceUpdateConcludedStateLocked(balancer.State{
ConnectivityState: connectivity.Connecting,
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
})
b.requestConnectionLocked()
b.startFirstPassLocked()
} else if b.state == connectivity.TransientFailure {
// If we're in TRANSIENT_FAILURE, we stay in TRANSIENT_FAILURE until
// we're READY. See A62.
b.requestConnectionLocked()
b.startFirstPassLocked()
}
return nil
}
@ -278,6 +340,7 @@ func (b *pickfirstBalancer) Close() {
b.mu.Lock()
defer b.mu.Unlock()
b.closeSubConnsLocked()
b.cancelConnectionTimer()
b.state = connectivity.Shutdown
}
@ -287,12 +350,21 @@ func (b *pickfirstBalancer) Close() {
func (b *pickfirstBalancer) ExitIdle() {
b.mu.Lock()
defer b.mu.Unlock()
if b.state == connectivity.Idle && b.addressList.currentAddress() == b.addressList.first() {
b.firstPass = true
b.requestConnectionLocked()
if b.state == connectivity.Idle {
b.startFirstPassLocked()
}
}
func (b *pickfirstBalancer) startFirstPassLocked() {
b.firstPass = true
b.numTF = 0
// Reset the connection attempt record for existing SubConns.
for _, sd := range b.subConns.Values() {
sd.(*scData).connectionFailedInFirstPass = false
}
b.requestConnectionLocked()
}
func (b *pickfirstBalancer) closeSubConnsLocked() {
for _, sd := range b.subConns.Values() {
sd.(*scData).subConn.Shutdown()
@ -314,6 +386,70 @@ func deDupAddresses(addrs []resolver.Address) []resolver.Address {
return retAddrs
}
// interleaveAddresses interleaves addresses of both families (IPv4 and IPv6)
// as per RFC-8305 section 4.
// Whichever address family is first in the list is followed by an address of
// the other address family; that is, if the first address in the list is IPv6,
// then the first IPv4 address should be moved up in the list to be second in
// the list. It doesn't support configuring "First Address Family Count", i.e.
// there will always be a single member of the first address family at the
// beginning of the interleaved list.
// Addresses that are neither IPv4 nor IPv6 are treated as part of a third
// "unknown" family for interleaving.
// See: https://datatracker.ietf.org/doc/html/rfc8305#autoid-6
func interleaveAddresses(addrs []resolver.Address) []resolver.Address {
familyAddrsMap := map[ipAddrFamily][]resolver.Address{}
interleavingOrder := []ipAddrFamily{}
for _, addr := range addrs {
family := addressFamily(addr.Addr)
if _, found := familyAddrsMap[family]; !found {
interleavingOrder = append(interleavingOrder, family)
}
familyAddrsMap[family] = append(familyAddrsMap[family], addr)
}
interleavedAddrs := make([]resolver.Address, 0, len(addrs))
for curFamilyIdx := 0; len(interleavedAddrs) < len(addrs); curFamilyIdx = (curFamilyIdx + 1) % len(interleavingOrder) {
// Some IP types may have fewer addresses than others, so we look for
// the next type that has a remaining member to add to the interleaved
// list.
family := interleavingOrder[curFamilyIdx]
remainingMembers := familyAddrsMap[family]
if len(remainingMembers) > 0 {
interleavedAddrs = append(interleavedAddrs, remainingMembers[0])
familyAddrsMap[family] = remainingMembers[1:]
}
}
return interleavedAddrs
}
// addressFamily returns the ipAddrFamily after parsing the address string.
// If the address isn't of the format "ip-address:port", it returns
// ipAddrFamilyUnknown. The address may be valid even if it's not an IP when
// using a resolver like passthrough where the address may be a hostname in
// some format that the dialer can resolve.
func addressFamily(address string) ipAddrFamily {
// Parse the IP after removing the port.
host, _, err := net.SplitHostPort(address)
if err != nil {
return ipAddrFamilyUnknown
}
ip, err := netip.ParseAddr(host)
if err != nil {
return ipAddrFamilyUnknown
}
switch {
case ip.Is4() || ip.Is4In6():
return ipAddrFamilyV4
case ip.Is6():
return ipAddrFamilyV6
default:
return ipAddrFamilyUnknown
}
}
// reconcileSubConnsLocked updates the active subchannels based on a new address
// list from the resolver. It does this by:
// - closing subchannels: any existing subchannels associated with addresses
@ -342,6 +478,7 @@ func (b *pickfirstBalancer) reconcileSubConnsLocked(newAddrs []resolver.Address)
// shutdownRemainingLocked shuts down remaining subConns. Called when a subConn
// becomes ready, which means that all other subConn must be shutdown.
func (b *pickfirstBalancer) shutdownRemainingLocked(selected *scData) {
b.cancelConnectionTimer()
for _, v := range b.subConns.Values() {
sd := v.(*scData)
if sd.subConn != selected.subConn {
@ -382,46 +519,89 @@ func (b *pickfirstBalancer) requestConnectionLocked() {
}
scd := sd.(*scData)
switch scd.state {
switch scd.rawConnectivityState {
case connectivity.Idle:
scd.subConn.Connect()
b.scheduleNextConnectionLocked()
return
case connectivity.TransientFailure:
// Try the next address.
// The SubConn is being re-used and failed during a previous pass
// over the addressList. It has not completed backoff yet.
// Mark it as having failed and try the next address.
scd.connectionFailedInFirstPass = true
lastErr = scd.lastErr
continue
case connectivity.Ready:
// Should never happen.
b.logger.Errorf("Requesting a connection even though we have a READY SubConn")
case connectivity.Shutdown:
// Should never happen.
b.logger.Errorf("SubConn with state SHUTDOWN present in SubConns map")
case connectivity.Connecting:
// Wait for the SubConn to report success or failure.
// Wait for the connection attempt to complete or the timer to fire
// before attempting the next address.
b.scheduleNextConnectionLocked()
return
default:
b.logger.Errorf("SubConn with unexpected state %v present in SubConns map.", scd.rawConnectivityState)
return
}
}
// All the remaining addresses in the list are in TRANSIENT_FAILURE, end the
// first pass if possible.
b.endFirstPassIfPossibleLocked(lastErr)
}
func (b *pickfirstBalancer) scheduleNextConnectionLocked() {
b.cancelConnectionTimer()
if !b.addressList.hasNext() {
return
}
// All the remaining addresses in the list are in TRANSIENT_FAILURE, end the
// first pass.
b.endFirstPassLocked(lastErr)
curAddr := b.addressList.currentAddress()
cancelled := false // Access to this is protected by the balancer's mutex.
closeFn := internal.TimeAfterFunc(connectionDelayInterval, func() {
b.mu.Lock()
defer b.mu.Unlock()
// If the scheduled task is cancelled while acquiring the mutex, return.
if cancelled {
return
}
if b.logger.V(2) {
b.logger.Infof("Happy Eyeballs timer expired while waiting for connection to %q.", curAddr.Addr)
}
if b.addressList.increment() {
b.requestConnectionLocked()
}
})
// Access to the cancellation callback held by the balancer is guarded by
// the balancer's mutex, so it's safe to set the boolean from the callback.
b.cancelConnectionTimer = sync.OnceFunc(func() {
cancelled = true
closeFn()
})
}
func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.SubConnState) {
b.mu.Lock()
defer b.mu.Unlock()
oldState := sd.state
sd.state = newState.ConnectivityState
oldState := sd.rawConnectivityState
sd.rawConnectivityState = newState.ConnectivityState
// Previously relevant SubConns can still callback with state updates.
// To prevent pickers from returning these obsolete SubConns, this logic
// is included to check if the current list of active SubConns includes this
// SubConn.
if activeSD, found := b.subConns.Get(sd.addr); !found || activeSD != sd {
if !b.isActiveSCData(sd) {
return
}
if newState.ConnectivityState == connectivity.Shutdown {
sd.effectiveState = connectivity.Shutdown
return
}
// Record a connection attempt when exiting CONNECTING.
if newState.ConnectivityState == connectivity.TransientFailure {
sd.connectionFailedInFirstPass = true
connectionAttemptsFailedMetric.Record(b.metricsRecorder, 1, b.target)
}
if newState.ConnectivityState == connectivity.Ready {
connectionAttemptsSucceededMetric.Record(b.metricsRecorder, 1, b.target)
b.shutdownRemainingLocked(sd)
if !b.addressList.seekTo(sd.addr) {
// This should not fail as we should have only one SubConn after
@ -429,10 +609,30 @@ func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.Sub
b.logger.Errorf("Address %q not found address list in %v", sd.addr, b.addressList.addresses)
return
}
b.state = connectivity.Ready
b.cc.UpdateState(balancer.State{
ConnectivityState: connectivity.Ready,
Picker: &picker{result: balancer.PickResult{SubConn: sd.subConn}},
if !b.healthCheckingEnabled {
if b.logger.V(2) {
b.logger.Infof("SubConn %p reported connectivity state READY and the health listener is disabled. Transitioning SubConn to READY.", sd.subConn)
}
sd.effectiveState = connectivity.Ready
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.Ready,
Picker: &picker{result: balancer.PickResult{SubConn: sd.subConn}},
})
return
}
if b.logger.V(2) {
b.logger.Infof("SubConn %p reported connectivity state READY. Registering health listener.", sd.subConn)
}
// Send a CONNECTING update to take the SubConn out of sticky-TF if
// required.
sd.effectiveState = connectivity.Connecting
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.Connecting,
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
})
sd.subConn.RegisterHealthListener(func(scs balancer.SubConnState) {
b.updateSubConnHealthState(sd, scs)
})
return
}
@ -443,13 +643,24 @@ func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.Sub
// a transport is successfully created, but the connection fails
// before the SubConn can send the notification for READY. We treat
// this as a successful connection and transition to IDLE.
if (b.state == connectivity.Ready && newState.ConnectivityState != connectivity.Ready) || (oldState == connectivity.Connecting && newState.ConnectivityState == connectivity.Idle) {
// TODO: https://github.com/grpc/grpc-go/issues/7862 - Remove the second
// part of the if condition below once the issue is fixed.
if oldState == connectivity.Ready || (oldState == connectivity.Connecting && newState.ConnectivityState == connectivity.Idle) {
// Once a transport fails, the balancer enters IDLE and starts from
// the first address when the picker is used.
b.shutdownRemainingLocked(sd)
b.state = connectivity.Idle
sd.effectiveState = newState.ConnectivityState
// READY SubConn interspliced in between CONNECTING and IDLE, need to
// account for that.
if oldState == connectivity.Connecting {
// A known issue (https://github.com/grpc/grpc-go/issues/7862)
// causes a race that prevents the READY state change notification.
// This works around it.
connectionAttemptsSucceededMetric.Record(b.metricsRecorder, 1, b.target)
}
disconnectionsMetric.Record(b.metricsRecorder, 1, b.target)
b.addressList.reset()
b.cc.UpdateState(balancer.State{
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.Idle,
Picker: &idlePicker{exitIdle: sync.OnceFunc(b.ExitIdle)},
})
@ -459,32 +670,35 @@ func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.Sub
if b.firstPass {
switch newState.ConnectivityState {
case connectivity.Connecting:
// The balancer can be in either IDLE, CONNECTING or
// TRANSIENT_FAILURE. If it's in TRANSIENT_FAILURE, stay in
// The effective state can be in either IDLE, CONNECTING or
// TRANSIENT_FAILURE. If it's TRANSIENT_FAILURE, stay in
// TRANSIENT_FAILURE until it's READY. See A62.
// If the balancer is already in CONNECTING, no update is needed.
if b.state == connectivity.Idle {
b.state = connectivity.Connecting
b.cc.UpdateState(balancer.State{
if sd.effectiveState != connectivity.TransientFailure {
sd.effectiveState = connectivity.Connecting
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.Connecting,
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
})
}
case connectivity.TransientFailure:
sd.lastErr = newState.ConnectionError
sd.effectiveState = connectivity.TransientFailure
// Since we're re-using common SubConns while handling resolver
// updates, we could receive an out of turn TRANSIENT_FAILURE from
// a pass over the previous address list. We ignore such updates.
// a pass over the previous address list. Happy Eyeballs will also
// cause out of order updates to arrive.
if curAddr := b.addressList.currentAddress(); !equalAddressIgnoringBalAttributes(&curAddr, &sd.addr) {
return
if curAddr := b.addressList.currentAddress(); equalAddressIgnoringBalAttributes(&curAddr, &sd.addr) {
b.cancelConnectionTimer()
if b.addressList.increment() {
b.requestConnectionLocked()
return
}
}
if b.addressList.increment() {
b.requestConnectionLocked()
return
}
// End of the first pass.
b.endFirstPassLocked(newState.ConnectionError)
// End the first pass if we've seen a TRANSIENT_FAILURE from all
// SubConns once.
b.endFirstPassIfPossibleLocked(newState.ConnectionError)
}
return
}
@ -495,7 +709,7 @@ func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.Sub
b.numTF = (b.numTF + 1) % b.subConns.Len()
sd.lastErr = newState.ConnectionError
if b.numTF%b.subConns.Len() == 0 {
b.cc.UpdateState(balancer.State{
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: &picker{err: newState.ConnectionError},
})
@ -509,24 +723,95 @@ func (b *pickfirstBalancer) updateSubConnState(sd *scData, newState balancer.Sub
}
}
func (b *pickfirstBalancer) endFirstPassLocked(lastErr error) {
// endFirstPassIfPossibleLocked ends the first happy-eyeballs pass if all the
// addresses are tried and their SubConns have reported a failure.
func (b *pickfirstBalancer) endFirstPassIfPossibleLocked(lastErr error) {
// An optimization to avoid iterating over the entire SubConn map.
if b.addressList.isValid() {
return
}
// Connect() has been called on all the SubConns. The first pass can be
// ended if all the SubConns have reported a failure.
for _, v := range b.subConns.Values() {
sd := v.(*scData)
if !sd.connectionFailedInFirstPass {
return
}
}
b.firstPass = false
b.numTF = 0
b.state = connectivity.TransientFailure
b.cc.UpdateState(balancer.State{
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: &picker{err: lastErr},
})
// Start re-connecting all the SubConns that are already in IDLE.
for _, v := range b.subConns.Values() {
sd := v.(*scData)
if sd.state == connectivity.Idle {
if sd.rawConnectivityState == connectivity.Idle {
sd.subConn.Connect()
}
}
}
func (b *pickfirstBalancer) isActiveSCData(sd *scData) bool {
activeSD, found := b.subConns.Get(sd.addr)
return found && activeSD == sd
}
func (b *pickfirstBalancer) updateSubConnHealthState(sd *scData, state balancer.SubConnState) {
b.mu.Lock()
defer b.mu.Unlock()
// Previously relevant SubConns can still callback with state updates.
// To prevent pickers from returning these obsolete SubConns, this logic
// is included to check if the current list of active SubConns includes
// this SubConn.
if !b.isActiveSCData(sd) {
return
}
sd.effectiveState = state.ConnectivityState
switch state.ConnectivityState {
case connectivity.Ready:
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.Ready,
Picker: &picker{result: balancer.PickResult{SubConn: sd.subConn}},
})
case connectivity.TransientFailure:
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.TransientFailure,
Picker: &picker{err: fmt.Errorf("pickfirst: health check failure: %v", state.ConnectionError)},
})
case connectivity.Connecting:
b.updateBalancerState(balancer.State{
ConnectivityState: connectivity.Connecting,
Picker: &picker{err: balancer.ErrNoSubConnAvailable},
})
default:
b.logger.Errorf("Got unexpected health update for SubConn %p: %v", state)
}
}
// updateBalancerState stores the state reported to the channel and calls
// ClientConn.UpdateState(). As an optimization, it avoids sending duplicate
// updates to the channel.
func (b *pickfirstBalancer) updateBalancerState(newState balancer.State) {
// In case of TransientFailures allow the picker to be updated to update
// the connectivity error, in all other cases don't send duplicate state
// updates.
if newState.ConnectivityState == b.state && b.state != connectivity.TransientFailure {
return
}
b.forceUpdateConcludedStateLocked(newState)
}
// forceUpdateConcludedStateLocked stores the state reported to the channel and
// calls ClientConn.UpdateState().
// A separate function is defined to force update the ClientConn state since the
// channel doesn't correctly assume that LB policies start in CONNECTING and
// relies on LB policy to send an initial CONNECTING update.
func (b *pickfirstBalancer) forceUpdateConcludedStateLocked(newState balancer.State) {
b.state = newState.ConnectivityState
b.cc.UpdateState(newState)
}
type picker struct {
result balancer.PickResult
err error
@ -583,15 +868,6 @@ func (al *addressList) currentAddress() resolver.Address {
return al.addresses[al.idx]
}
// first returns the first address in the list. If the list is empty, it returns
// an empty address instead.
func (al *addressList) first() resolver.Address {
if len(al.addresses) == 0 {
return resolver.Address{}
}
return al.addresses[0]
}
func (al *addressList) reset() {
al.idx = 0
}
@ -614,6 +890,16 @@ func (al *addressList) seekTo(needle resolver.Address) bool {
return false
}
// hasNext returns whether incrementing the addressList will result in moving
// past the end of the list. If the list has already moved past the end, it
// returns false.
func (al *addressList) hasNext() bool {
if !al.isValid() {
return false
}
return al.idx+1 < len(al.addresses)
}
// equalAddressIgnoringBalAttributes returns true is a and b are considered
// equal. This is different from the Equal method on the resolver.Address type
// which considers all fields to determine equality. Here, we only consider

View File

@ -22,7 +22,7 @@
package roundrobin
import (
"math/rand"
rand "math/rand/v2"
"sync/atomic"
"google.golang.org/grpc/balancer"
@ -60,7 +60,7 @@ func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
// Start at a random index, as the same RR balancer rebuilds a new
// picker when SubConn states change, and we don't want to apply excess
// load to the first server in the list.
next: uint32(rand.Intn(len(scs))),
next: uint32(rand.IntN(len(scs))),
}
}

134
vendor/google.golang.org/grpc/balancer/subconn.go generated vendored Normal file
View File

@ -0,0 +1,134 @@
/*
*
* Copyright 2024 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 balancer
import (
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/resolver"
)
// A SubConn represents a single connection to a gRPC backend service.
//
// All SubConns start in IDLE, and will not try to connect. To trigger a
// connection attempt, Balancers must call Connect.
//
// If the connection attempt fails, the SubConn will transition to
// TRANSIENT_FAILURE for a backoff period, and then return to IDLE. If the
// connection attempt succeeds, it will transition to READY.
//
// If a READY SubConn becomes disconnected, the SubConn will transition to IDLE.
//
// If a connection re-enters IDLE, Balancers must call Connect again to trigger
// a new connection attempt.
//
// Each SubConn contains a list of addresses. gRPC will try to connect to the
// addresses in sequence, and stop trying the remainder once the first
// connection is successful. However, this behavior is deprecated. SubConns
// should only use a single address.
//
// NOTICE: This interface is intended to be implemented by gRPC, or intercepted
// by custom load balancing poilices. Users should not need their own complete
// implementation of this interface -- they should always delegate to a SubConn
// returned by ClientConn.NewSubConn() by embedding it in their implementations.
// An embedded SubConn must never be nil, or runtime panics will occur.
type SubConn interface {
// UpdateAddresses updates the addresses used in this SubConn.
// gRPC checks if currently-connected address is still in the new list.
// If it's in the list, the connection will be kept.
// If it's not in the list, the connection will gracefully close, and
// a new connection will be created.
//
// This will trigger a state transition for the SubConn.
//
// Deprecated: this method will be removed. Create new SubConns for new
// addresses instead.
UpdateAddresses([]resolver.Address)
// Connect starts the connecting for this SubConn.
Connect()
// GetOrBuildProducer returns a reference to the existing Producer for this
// ProducerBuilder in this SubConn, or, if one does not currently exist,
// creates a new one and returns it. Returns a close function which may be
// called when the Producer is no longer needed. Otherwise the producer
// will automatically be closed upon connection loss or subchannel close.
// Should only be called on a SubConn in state Ready. Otherwise the
// producer will be unable to create streams.
GetOrBuildProducer(ProducerBuilder) (p Producer, close func())
// Shutdown shuts down the SubConn gracefully. Any started RPCs will be
// allowed to complete. No future calls should be made on the SubConn.
// One final state update will be delivered to the StateListener (or
// UpdateSubConnState; deprecated) with ConnectivityState of Shutdown to
// indicate the shutdown operation. This may be delivered before
// in-progress RPCs are complete and the actual connection is closed.
Shutdown()
// RegisterHealthListener registers a health listener that receives health
// updates for a Ready SubConn. Only one health listener can be registered
// at a time. A health listener should be registered each time the SubConn's
// connectivity state changes to READY. Registering a health listener when
// the connectivity state is not READY may result in undefined behaviour.
// This method must not be called synchronously while handling an update
// from a previously registered health listener.
RegisterHealthListener(func(SubConnState))
// EnforceSubConnEmbedding is included to force implementers to embed
// another implementation of this interface, allowing gRPC to add methods
// without breaking users.
internal.EnforceSubConnEmbedding
}
// A ProducerBuilder is a simple constructor for a Producer. It is used by the
// SubConn to create producers when needed.
type ProducerBuilder interface {
// Build creates a Producer. The first parameter is always a
// grpc.ClientConnInterface (a type to allow creating RPCs/streams on the
// associated SubConn), but is declared as `any` to avoid a dependency
// cycle. Build also returns a close function that will be called when all
// references to the Producer have been given up for a SubConn, or when a
// connectivity state change occurs on the SubConn. The close function
// should always block until all asynchronous cleanup work is completed.
Build(grpcClientConnInterface any) (p Producer, close func())
}
// SubConnState describes the state of a SubConn.
type SubConnState struct {
// ConnectivityState is the connectivity state of the SubConn.
ConnectivityState connectivity.State
// ConnectionError is set if the ConnectivityState is TransientFailure,
// describing the reason the SubConn failed. Otherwise, it is nil.
ConnectionError error
// connectedAddr contains the connected address when ConnectivityState is
// Ready. Otherwise, it is indeterminate.
connectedAddress resolver.Address
}
// connectedAddress returns the connected address for a SubConnState. The
// address is only valid if the state is READY.
func connectedAddress(scs SubConnState) resolver.Address {
return scs.connectedAddress
}
// setConnectedAddress sets the connected address for a SubConnState.
func setConnectedAddress(scs *SubConnState, addr resolver.Address) {
scs.connectedAddress = addr
}
// A Producer is a type shared among potentially many consumers. It is
// associated with a SubConn, and an implementation will typically contain
// other methods to provide additional functionality, e.g. configuration or
// subscription registration.
type Producer any

View File

@ -189,6 +189,7 @@ func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer
ac: ac,
producers: make(map[balancer.ProducerBuilder]*refCountedProducer),
stateListener: opts.StateListener,
healthData: newHealthData(connectivity.Idle),
}
ac.acbw = acbw
return acbw, nil
@ -254,12 +255,32 @@ func (ccb *ccBalancerWrapper) Target() string {
// acBalancerWrapper is a wrapper on top of ac for balancers.
// It implements balancer.SubConn interface.
type acBalancerWrapper struct {
internal.EnforceSubConnEmbedding
ac *addrConn // read-only
ccb *ccBalancerWrapper // read-only
stateListener func(balancer.SubConnState)
producersMu sync.Mutex
producers map[balancer.ProducerBuilder]*refCountedProducer
// Access to healthData is protected by healthMu.
healthMu sync.Mutex
// healthData is stored as a pointer to detect when the health listener is
// dropped or updated. This is required as closures can't be compared for
// equality.
healthData *healthData
}
// healthData holds data related to health state reporting.
type healthData struct {
// connectivityState stores the most recent connectivity state delivered
// to the LB policy. This is stored to avoid sending updates when the
// SubConn has already exited connectivity state READY.
connectivityState connectivity.State
}
func newHealthData(s connectivity.State) *healthData {
return &healthData{connectivityState: s}
}
// updateState is invoked by grpc to push a subConn state update to the
@ -279,6 +300,24 @@ func (acbw *acBalancerWrapper) updateState(s connectivity.State, curAddr resolve
if s == connectivity.Ready {
setConnectedAddress(&scs, curAddr)
}
// Invalidate the health listener by updating the healthData.
acbw.healthMu.Lock()
// A race may occur if a health listener is registered soon after the
// connectivity state is set but before the stateListener is called.
// Two cases may arise:
// 1. The new state is not READY: RegisterHealthListener has checks to
// ensure no updates are sent when the connectivity state is not
// READY.
// 2. The new state is READY: This means that the old state wasn't Ready.
// The RegisterHealthListener API mentions that a health listener
// must not be registered when a SubConn is not ready to avoid such
// races. When this happens, the LB policy would get health updates
// on the old listener. When the LB policy registers a new listener
// on receiving the connectivity update, the health updates will be
// sent to the new health listener.
acbw.healthData = newHealthData(scs.ConnectivityState)
acbw.healthMu.Unlock()
acbw.stateListener(scs)
})
}
@ -373,3 +412,41 @@ func (acbw *acBalancerWrapper) closeProducers() {
delete(acbw.producers, pb)
}
}
// RegisterHealthListener accepts a health listener from the LB policy. It sends
// updates to the health listener as long as the SubConn's connectivity state
// doesn't change and a new health listener is not registered. To invalidate
// the currently registered health listener, acbw updates the healthData. If a
// nil listener is registered, the active health listener is dropped.
func (acbw *acBalancerWrapper) RegisterHealthListener(listener func(balancer.SubConnState)) {
acbw.healthMu.Lock()
defer acbw.healthMu.Unlock()
// listeners should not be registered when the connectivity state
// isn't Ready. This may happen when the balancer registers a listener
// after the connectivityState is updated, but before it is notified
// of the update.
if acbw.healthData.connectivityState != connectivity.Ready {
return
}
// Replace the health data to stop sending updates to any previously
// registered health listeners.
hd := newHealthData(connectivity.Ready)
acbw.healthData = hd
if listener == nil {
return
}
acbw.ccb.serializer.TrySchedule(func(ctx context.Context) {
if ctx.Err() != nil || acbw.ccb.balancer == nil {
return
}
// Don't send updates if a new listener is registered.
acbw.healthMu.Lock()
defer acbw.healthMu.Unlock()
curHD := acbw.healthData
if curHD != hd {
return
}
listener(balancer.SubConnState{ConnectivityState: connectivity.Ready})
})
}

View File

@ -18,7 +18,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc-gen-go v1.35.1
// protoc v5.27.1
// source: grpc/binlog/v1/binarylog.proto
@ -274,11 +274,9 @@ type GrpcLogEntry struct {
func (x *GrpcLogEntry) Reset() {
*x = GrpcLogEntry{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GrpcLogEntry) String() string {
@ -289,7 +287,7 @@ func (*GrpcLogEntry) ProtoMessage() {}
func (x *GrpcLogEntry) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -440,11 +438,9 @@ type ClientHeader struct {
func (x *ClientHeader) Reset() {
*x = ClientHeader{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ClientHeader) String() string {
@ -455,7 +451,7 @@ func (*ClientHeader) ProtoMessage() {}
func (x *ClientHeader) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -509,11 +505,9 @@ type ServerHeader struct {
func (x *ServerHeader) Reset() {
*x = ServerHeader{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ServerHeader) String() string {
@ -524,7 +518,7 @@ func (*ServerHeader) ProtoMessage() {}
func (x *ServerHeader) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -565,11 +559,9 @@ type Trailer struct {
func (x *Trailer) Reset() {
*x = Trailer{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Trailer) String() string {
@ -580,7 +572,7 @@ func (*Trailer) ProtoMessage() {}
func (x *Trailer) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -638,11 +630,9 @@ type Message struct {
func (x *Message) Reset() {
*x = Message{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Message) String() string {
@ -653,7 +643,7 @@ func (*Message) ProtoMessage() {}
func (x *Message) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -713,11 +703,9 @@ type Metadata struct {
func (x *Metadata) Reset() {
*x = Metadata{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Metadata) String() string {
@ -728,7 +716,7 @@ func (*Metadata) ProtoMessage() {}
func (x *Metadata) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -762,11 +750,9 @@ type MetadataEntry struct {
func (x *MetadataEntry) Reset() {
*x = MetadataEntry{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *MetadataEntry) String() string {
@ -777,7 +763,7 @@ func (*MetadataEntry) ProtoMessage() {}
func (x *MetadataEntry) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -820,11 +806,9 @@ type Address struct {
func (x *Address) Reset() {
*x = Address{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Address) String() string {
@ -835,7 +819,7 @@ func (*Address) ProtoMessage() {}
func (x *Address) ProtoReflect() protoreflect.Message {
mi := &file_grpc_binlog_v1_binarylog_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -1057,104 +1041,6 @@ func file_grpc_binlog_v1_binarylog_proto_init() {
if File_grpc_binlog_v1_binarylog_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_grpc_binlog_v1_binarylog_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*GrpcLogEntry); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*ClientHeader); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*ServerHeader); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*Trailer); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*Message); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*Metadata); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*MetadataEntry); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*Address); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_grpc_binlog_v1_binarylog_proto_msgTypes[0].OneofWrappers = []any{
(*GrpcLogEntry_ClientHeader)(nil),
(*GrpcLogEntry_ServerHeader)(nil),

View File

@ -775,10 +775,7 @@ func (cc *ClientConn) updateResolverStateAndUnlock(s resolver.State, err error)
}
}
var balCfg serviceconfig.LoadBalancingConfig
if cc.sc != nil && cc.sc.lbConfig != nil {
balCfg = cc.sc.lbConfig
}
balCfg := cc.sc.lbConfig
bw := cc.balancerWrapper
cc.mu.Unlock()
@ -1374,7 +1371,7 @@ func (ac *addrConn) createTransport(ctx context.Context, addr resolver.Address,
defer cancel()
copts.ChannelzParent = ac.channelz
newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, addr, copts, onClose)
newTr, err := transport.NewHTTP2Client(connectCtx, ac.cc.ctx, addr, copts, onClose)
if err != nil {
if logger.V(2) {
logger.Infof("Creating new client transport to %q: %v", addr, err)
@ -1448,7 +1445,7 @@ func (ac *addrConn) startHealthCheck(ctx context.Context) {
if !ac.scopts.HealthCheckEnabled {
return
}
healthCheckFunc := ac.cc.dopts.healthCheckFunc
healthCheckFunc := internal.HealthCheckFunc
if healthCheckFunc == nil {
// The health package is not imported to set health check function.
//
@ -1480,7 +1477,7 @@ func (ac *addrConn) startHealthCheck(ctx context.Context) {
}
// Start the health checking stream.
go func() {
err := ac.cc.dopts.healthCheckFunc(ctx, newStream, setConnectivityState, healthCheckConfig.ServiceName)
err := healthCheckFunc(ctx, newStream, setConnectivityState, healthCheckConfig.ServiceName)
if err != nil {
if status.Code(err) == codes.Unimplemented {
channelz.Error(logger, ac.channelz, "Subchannel health check is unimplemented at server side, thus health check is disabled")

View File

@ -71,7 +71,7 @@ func (c codecV0Bridge) Marshal(v any) (mem.BufferSlice, error) {
if err != nil {
return nil, err
}
return mem.BufferSlice{mem.NewBuffer(&data, nil)}, nil
return mem.BufferSlice{mem.SliceBuffer(data)}, nil
}
func (c codecV0Bridge) Unmarshal(data mem.BufferSlice, v any) (err error) {

View File

@ -87,7 +87,6 @@ type dialOptions struct {
disableServiceConfig bool
disableRetry bool
disableHealthCheck bool
healthCheckFunc internal.HealthChecker
minConnectTimeout func() time.Duration
defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON.
defaultServiceConfigRawJSON *string
@ -445,10 +444,6 @@ func WithContextDialer(f func(context.Context, string) (net.Conn, error)) DialOp
})
}
func init() {
internal.WithHealthCheckFunc = withHealthCheckFunc
}
// WithDialer returns a DialOption that specifies a function to use for dialing
// network addresses. If FailOnNonTempDialError() is set to true, and an error
// is returned by f, gRPC checks the error's Temporary() method to decide if it
@ -662,16 +657,6 @@ func WithDisableHealthCheck() DialOption {
})
}
// withHealthCheckFunc replaces the default health check function with the
// provided one. It makes tests easier to change the health check function.
//
// For testing purpose only.
func withHealthCheckFunc(f internal.HealthChecker) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.healthCheckFunc = f
})
}
func defaultDialOptions() dialOptions {
return dialOptions{
copts: transport.ConnectOptions{
@ -682,7 +667,6 @@ func defaultDialOptions() dialOptions {
BufferPool: mem.DefaultBufferPool(),
},
bs: internalbackoff.DefaultExponential,
healthCheckFunc: internal.HealthCheckFunc,
idleTimeout: 30 * time.Minute,
defaultScheme: "dns",
maxCallAttempts: defaultMaxCallAttempts,

View File

@ -23,6 +23,7 @@ import (
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/stats"
)
func init() {
@ -34,7 +35,7 @@ var logger = grpclog.Component("metrics-registry")
// DefaultMetrics are the default metrics registered through global metrics
// registry. This is written to at initialization time only, and is read only
// after initialization.
var DefaultMetrics = NewMetrics()
var DefaultMetrics = stats.NewMetricSet()
// MetricDescriptor is the data for a registered metric.
type MetricDescriptor struct {
@ -42,7 +43,7 @@ type MetricDescriptor struct {
// (including any per call metrics). See
// https://github.com/grpc/proposal/blob/master/A79-non-per-call-metrics-architecture.md#metric-instrument-naming-conventions
// for metric naming conventions.
Name Metric
Name string
// The description of this metric.
Description string
// The unit (e.g. entries, seconds) of this metric.
@ -154,27 +155,27 @@ func (h *Int64GaugeHandle) Record(recorder MetricsRecorder, incr int64, labels .
}
// registeredMetrics are the registered metric descriptor names.
var registeredMetrics = make(map[Metric]bool)
var registeredMetrics = make(map[string]bool)
// metricsRegistry contains all of the registered metrics.
//
// This is written to only at init time, and read only after that.
var metricsRegistry = make(map[Metric]*MetricDescriptor)
var metricsRegistry = make(map[string]*MetricDescriptor)
// DescriptorForMetric returns the MetricDescriptor from the global registry.
//
// Returns nil if MetricDescriptor not present.
func DescriptorForMetric(metric Metric) *MetricDescriptor {
return metricsRegistry[metric]
func DescriptorForMetric(metricName string) *MetricDescriptor {
return metricsRegistry[metricName]
}
func registerMetric(name Metric, def bool) {
if registeredMetrics[name] {
logger.Fatalf("metric %v already registered", name)
func registerMetric(metricName string, def bool) {
if registeredMetrics[metricName] {
logger.Fatalf("metric %v already registered", metricName)
}
registeredMetrics[name] = true
registeredMetrics[metricName] = true
if def {
DefaultMetrics = DefaultMetrics.Add(name)
DefaultMetrics = DefaultMetrics.Add(metricName)
}
}
@ -256,8 +257,8 @@ func snapshotMetricsRegistryForTesting() func() {
oldRegisteredMetrics := registeredMetrics
oldMetricsRegistry := metricsRegistry
registeredMetrics = make(map[Metric]bool)
metricsRegistry = make(map[Metric]*MetricDescriptor)
registeredMetrics = make(map[string]bool)
metricsRegistry = make(map[string]*MetricDescriptor)
maps.Copy(registeredMetrics, registeredMetrics)
maps.Copy(metricsRegistry, metricsRegistry)

View File

@ -19,7 +19,7 @@
// Package stats contains experimental metrics/stats API's.
package stats
import "maps"
import "google.golang.org/grpc/stats"
// MetricsRecorder records on metrics derived from metric registry.
type MetricsRecorder interface {
@ -40,75 +40,15 @@ type MetricsRecorder interface {
RecordInt64Gauge(handle *Int64GaugeHandle, incr int64, labels ...string)
}
// Metric is an identifier for a metric.
type Metric string
// Metrics is an experimental legacy alias of the now-stable stats.MetricSet.
// Metrics will be deleted in a future release.
type Metrics = stats.MetricSet
// Metrics is a set of metrics to record. Once created, Metrics is immutable,
// however Add and Remove can make copies with specific metrics added or
// removed, respectively.
//
// Do not construct directly; use NewMetrics instead.
type Metrics struct {
// metrics are the set of metrics to initialize.
metrics map[Metric]bool
}
// Metric was replaced by direct usage of strings.
type Metric = string
// NewMetrics returns a Metrics containing Metrics.
// NewMetrics is an experimental legacy alias of the now-stable
// stats.NewMetricSet. NewMetrics will be deleted in a future release.
func NewMetrics(metrics ...Metric) *Metrics {
newMetrics := make(map[Metric]bool)
for _, metric := range metrics {
newMetrics[metric] = true
}
return &Metrics{
metrics: newMetrics,
}
}
// Metrics returns the metrics set. The returned map is read-only and must not
// be modified.
func (m *Metrics) Metrics() map[Metric]bool {
return m.metrics
}
// Add adds the metrics to the metrics set and returns a new copy with the
// additional metrics.
func (m *Metrics) Add(metrics ...Metric) *Metrics {
newMetrics := make(map[Metric]bool)
for metric := range m.metrics {
newMetrics[metric] = true
}
for _, metric := range metrics {
newMetrics[metric] = true
}
return &Metrics{
metrics: newMetrics,
}
}
// Join joins the metrics passed in with the metrics set, and returns a new copy
// with the merged metrics.
func (m *Metrics) Join(metrics *Metrics) *Metrics {
newMetrics := make(map[Metric]bool)
maps.Copy(newMetrics, m.metrics)
maps.Copy(newMetrics, metrics.metrics)
return &Metrics{
metrics: newMetrics,
}
}
// Remove removes the metrics from the metrics set and returns a new copy with
// the metrics removed.
func (m *Metrics) Remove(metrics ...Metric) *Metrics {
newMetrics := make(map[Metric]bool)
for metric := range m.metrics {
newMetrics[metric] = true
}
for _, metric := range metrics {
delete(newMetrics, metric)
}
return &Metrics{
metrics: newMetrics,
}
return stats.NewMetricSet(metrics...)
}

View File

@ -101,6 +101,22 @@ var severityName = []string{
fatalLog: "FATAL",
}
// sprintf is fmt.Sprintf.
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
var sprintf = fmt.Sprintf
// sprint is fmt.Sprint.
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
var sprint = fmt.Sprint
// sprintln is fmt.Sprintln.
// These vars exist to make it possible to test that expensive format calls aren't made unnecessarily.
var sprintln = fmt.Sprintln
// exit is os.Exit.
// This var exists to make it possible to test functions calling os.Exit.
var exit = os.Exit
// loggerT is the default logger used by grpclog.
type loggerT struct {
m []*log.Logger
@ -111,7 +127,7 @@ type loggerT struct {
func (g *loggerT) output(severity int, s string) {
sevStr := severityName[severity]
if !g.jsonFormat {
g.m[severity].Output(2, fmt.Sprintf("%v: %v", sevStr, s))
g.m[severity].Output(2, sevStr+": "+s)
return
}
// TODO: we can also include the logging component, but that needs more
@ -123,55 +139,79 @@ func (g *loggerT) output(severity int, s string) {
g.m[severity].Output(2, string(b))
}
func (g *loggerT) printf(severity int, format string, args ...any) {
// Note the discard check is duplicated in each print func, rather than in
// output, to avoid the expensive Sprint calls.
// De-duplicating this by moving to output would be a significant performance regression!
if lg := g.m[severity]; lg.Writer() == io.Discard {
return
}
g.output(severity, sprintf(format, args...))
}
func (g *loggerT) print(severity int, v ...any) {
if lg := g.m[severity]; lg.Writer() == io.Discard {
return
}
g.output(severity, sprint(v...))
}
func (g *loggerT) println(severity int, v ...any) {
if lg := g.m[severity]; lg.Writer() == io.Discard {
return
}
g.output(severity, sprintln(v...))
}
func (g *loggerT) Info(args ...any) {
g.output(infoLog, fmt.Sprint(args...))
g.print(infoLog, args...)
}
func (g *loggerT) Infoln(args ...any) {
g.output(infoLog, fmt.Sprintln(args...))
g.println(infoLog, args...)
}
func (g *loggerT) Infof(format string, args ...any) {
g.output(infoLog, fmt.Sprintf(format, args...))
g.printf(infoLog, format, args...)
}
func (g *loggerT) Warning(args ...any) {
g.output(warningLog, fmt.Sprint(args...))
g.print(warningLog, args...)
}
func (g *loggerT) Warningln(args ...any) {
g.output(warningLog, fmt.Sprintln(args...))
g.println(warningLog, args...)
}
func (g *loggerT) Warningf(format string, args ...any) {
g.output(warningLog, fmt.Sprintf(format, args...))
g.printf(warningLog, format, args...)
}
func (g *loggerT) Error(args ...any) {
g.output(errorLog, fmt.Sprint(args...))
g.print(errorLog, args...)
}
func (g *loggerT) Errorln(args ...any) {
g.output(errorLog, fmt.Sprintln(args...))
g.println(errorLog, args...)
}
func (g *loggerT) Errorf(format string, args ...any) {
g.output(errorLog, fmt.Sprintf(format, args...))
g.printf(errorLog, format, args...)
}
func (g *loggerT) Fatal(args ...any) {
g.output(fatalLog, fmt.Sprint(args...))
os.Exit(1)
g.print(fatalLog, args...)
exit(1)
}
func (g *loggerT) Fatalln(args ...any) {
g.output(fatalLog, fmt.Sprintln(args...))
os.Exit(1)
g.println(fatalLog, args...)
exit(1)
}
func (g *loggerT) Fatalf(format string, args ...any) {
g.output(fatalLog, fmt.Sprintf(format, args...))
os.Exit(1)
g.printf(fatalLog, format, args...)
exit(1)
}
func (g *loggerT) V(l int) bool {
@ -186,19 +226,42 @@ type LoggerV2Config struct {
FormatJSON bool
}
// combineLoggers returns a combined logger for both higher & lower severity logs,
// or only one if the other is io.Discard.
//
// This uses io.Discard instead of io.MultiWriter when all loggers
// are set to io.Discard. Both this package and the standard log package have
// significant optimizations for io.Discard, which io.MultiWriter lacks (as of
// this writing).
func combineLoggers(lower, higher io.Writer) io.Writer {
if lower == io.Discard {
return higher
}
if higher == io.Discard {
return lower
}
return io.MultiWriter(lower, higher)
}
// NewLoggerV2 creates a new LoggerV2 instance with the provided configuration.
// The infoW, warningW, and errorW writers are used to write log messages of
// different severity levels.
func NewLoggerV2(infoW, warningW, errorW io.Writer, c LoggerV2Config) LoggerV2 {
var m []*log.Logger
flag := log.LstdFlags
if c.FormatJSON {
flag = 0
}
m = append(m, log.New(infoW, "", flag))
m = append(m, log.New(io.MultiWriter(infoW, warningW), "", flag))
ew := io.MultiWriter(infoW, warningW, errorW) // ew will be used for error and fatal.
m = append(m, log.New(ew, "", flag))
m = append(m, log.New(ew, "", flag))
warningW = combineLoggers(infoW, warningW)
errorW = combineLoggers(errorW, warningW)
fatalW := errorW
m := []*log.Logger{
log.New(infoW, "", flag),
log.New(warningW, "", flag),
log.New(errorW, "", flag),
log.New(fatalW, "", flag),
}
return &loggerT{m: m, v: c.Verbosity, jsonFormat: c.FormatJSON}
}

View File

@ -17,7 +17,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc-gen-go v1.35.1
// protoc v5.27.1
// source: grpc/health/v1/health.proto
@ -99,11 +99,9 @@ type HealthCheckRequest struct {
func (x *HealthCheckRequest) Reset() {
*x = HealthCheckRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_health_v1_health_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_health_v1_health_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HealthCheckRequest) String() string {
@ -114,7 +112,7 @@ func (*HealthCheckRequest) ProtoMessage() {}
func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message {
mi := &file_grpc_health_v1_health_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -146,11 +144,9 @@ type HealthCheckResponse struct {
func (x *HealthCheckResponse) Reset() {
*x = HealthCheckResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_health_v1_health_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
mi := &file_grpc_health_v1_health_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *HealthCheckResponse) String() string {
@ -161,7 +157,7 @@ func (*HealthCheckResponse) ProtoMessage() {}
func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message {
mi := &file_grpc_health_v1_health_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@ -260,32 +256,6 @@ func file_grpc_health_v1_health_proto_init() {
if File_grpc_health_v1_health_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_grpc_health_v1_health_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*HealthCheckRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_grpc_health_v1_health_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*HealthCheckResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{

View File

@ -25,7 +25,7 @@ package backoff
import (
"context"
"errors"
"math/rand"
rand "math/rand/v2"
"time"
grpcbackoff "google.golang.org/grpc/backoff"

View File

@ -29,8 +29,6 @@ import (
)
var (
// WithHealthCheckFunc is set by dialoptions.go
WithHealthCheckFunc any // func (HealthChecker) DialOption
// HealthCheckFunc is used to provide client-side LB channel health checking
HealthCheckFunc HealthChecker
// BalancerUnregister is exported by package balancer to unregister a balancer.
@ -149,6 +147,20 @@ var (
// other features, including the CSDS service.
NewXDSResolverWithConfigForTesting any // func([]byte) (resolver.Builder, error)
// NewXDSResolverWithClientForTesting creates a new xDS resolver builder
// using the provided xDS client instead of creating a new one using the
// bootstrap configuration specified by the supported environment variables.
// The resolver.Builder is meant to be used in conjunction with the
// grpc.WithResolvers DialOption. The resolver.Builder does not take
// ownership of the provided xDS client and it is the responsibility of the
// caller to close the client when no longer required.
//
// Testing Only
//
// This function should ONLY be used for testing and may not work with some
// other features, including the CSDS service.
NewXDSResolverWithClientForTesting any // func(xdsclient.XDSClient) (resolver.Builder, error)
// RegisterRLSClusterSpecifierPluginForTesting registers the RLS Cluster
// Specifier Plugin for testing purposes, regardless of the XDSRLS environment
// variable.
@ -255,3 +267,9 @@ const (
// It currently has an experimental suffix which would be removed once
// end-to-end testing of the policy is completed.
const RLSLoadBalancingPolicyName = "rls_experimental"
// EnforceSubConnEmbedding is used to enforce proper SubConn implementation
// embedding.
type EnforceSubConnEmbedding interface {
enforceSubConnEmbedding()
}

View File

@ -24,8 +24,9 @@ import (
"context"
"encoding/json"
"fmt"
"math/rand"
rand "math/rand/v2"
"net"
"net/netip"
"os"
"strconv"
"strings"
@ -122,7 +123,7 @@ func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts
}
// IP address.
if ipAddr, ok := formatIP(host); ok {
if ipAddr, err := formatIP(host); err == nil {
addr := []resolver.Address{{Addr: ipAddr + ":" + port}}
cc.UpdateState(resolver.State{Addresses: addr})
return deadResolver{}, nil
@ -237,7 +238,9 @@ func (d *dnsResolver) watcher() {
}
func (d *dnsResolver) lookupSRV(ctx context.Context) ([]resolver.Address, error) {
if !EnableSRVLookups {
// Skip this particular host to avoid timeouts with some versions of
// systemd-resolved.
if !EnableSRVLookups || d.host == "metadata.google.internal." {
return nil, nil
}
var newAddrs []resolver.Address
@ -258,9 +261,9 @@ func (d *dnsResolver) lookupSRV(ctx context.Context) ([]resolver.Address, error)
return nil, err
}
for _, a := range lbAddrs {
ip, ok := formatIP(a)
if !ok {
return nil, fmt.Errorf("dns: error parsing A record IP address %v", a)
ip, err := formatIP(a)
if err != nil {
return nil, fmt.Errorf("dns: error parsing A record IP address %v: %v", a, err)
}
addr := ip + ":" + strconv.Itoa(int(s.Port))
newAddrs = append(newAddrs, resolver.Address{Addr: addr, ServerName: s.Target})
@ -320,9 +323,9 @@ func (d *dnsResolver) lookupHost(ctx context.Context) ([]resolver.Address, error
}
newAddrs := make([]resolver.Address, 0, len(addrs))
for _, a := range addrs {
ip, ok := formatIP(a)
if !ok {
return nil, fmt.Errorf("dns: error parsing A record IP address %v", a)
ip, err := formatIP(a)
if err != nil {
return nil, fmt.Errorf("dns: error parsing A record IP address %v: %v", a, err)
}
addr := ip + ":" + d.port
newAddrs = append(newAddrs, resolver.Address{Addr: addr})
@ -349,19 +352,19 @@ func (d *dnsResolver) lookup() (*resolver.State, error) {
return &state, nil
}
// formatIP returns ok = false if addr is not a valid textual representation of
// an IP address. If addr is an IPv4 address, return the addr and ok = true.
// formatIP returns an error if addr is not a valid textual representation of
// an IP address. If addr is an IPv4 address, return the addr and error = nil.
// If addr is an IPv6 address, return the addr enclosed in square brackets and
// ok = true.
func formatIP(addr string) (addrIP string, ok bool) {
ip := net.ParseIP(addr)
if ip == nil {
return "", false
// error = nil.
func formatIP(addr string) (string, error) {
ip, err := netip.ParseAddr(addr)
if err != nil {
return "", err
}
if ip.To4() != nil {
return addr, true
if ip.Is4() {
return addr, nil
}
return "[" + addr + "]", true
return "[" + addr + "]", nil
}
// parseTarget takes the user input target string and default port, returns
@ -377,7 +380,7 @@ func parseTarget(target, defaultPort string) (host, port string, err error) {
if target == "" {
return "", "", internal.ErrMissingAddr
}
if ip := net.ParseIP(target); ip != nil {
if _, err := netip.ParseAddr(target); err == nil {
// target is an IPv4 or IPv6(without brackets) address
return target, defaultPort, nil
}
@ -425,7 +428,7 @@ func chosenByPercentage(a *int) bool {
if a == nil {
return true
}
return rand.Intn(100)+1 <= *a
return rand.IntN(100)+1 <= *a
}
func canaryingSC(js string) string {

View File

@ -0,0 +1,144 @@
/*
*
* Copyright 2024 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 transport
import (
"sync/atomic"
"golang.org/x/net/http2"
"google.golang.org/grpc/mem"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// ClientStream implements streaming functionality for a gRPC client.
type ClientStream struct {
*Stream // Embed for common stream functionality.
ct *http2Client
done chan struct{} // closed at the end of stream to unblock writers.
doneFunc func() // invoked at the end of stream.
headerChan chan struct{} // closed to indicate the end of header metadata.
headerChanClosed uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times.
// headerValid indicates whether a valid header was received. Only
// meaningful after headerChan is closed (always call waitOnHeader() before
// reading its value).
headerValid bool
header metadata.MD // the received header metadata
noHeaders bool // set if the client never received headers (set only after the stream is done).
bytesReceived atomic.Bool // indicates whether any bytes have been received on this stream
unprocessed atomic.Bool // set if the server sends a refused stream or GOAWAY including this stream
status *status.Status // the status error received from the server
}
// Read reads an n byte message from the input stream.
func (s *ClientStream) Read(n int) (mem.BufferSlice, error) {
b, err := s.Stream.read(n)
if err == nil {
s.ct.incrMsgRecv()
}
return b, err
}
// Close closes the stream and popagates err to any readers.
func (s *ClientStream) Close(err error) {
var (
rst bool
rstCode http2.ErrCode
)
if err != nil {
rst = true
rstCode = http2.ErrCodeCancel
}
s.ct.closeStream(s, err, rst, rstCode, status.Convert(err), nil, false)
}
// Write writes the hdr and data bytes to the output stream.
func (s *ClientStream) Write(hdr []byte, data mem.BufferSlice, opts *WriteOptions) error {
return s.ct.write(s, hdr, data, opts)
}
// BytesReceived indicates whether any bytes have been received on this stream.
func (s *ClientStream) BytesReceived() bool {
return s.bytesReceived.Load()
}
// Unprocessed indicates whether the server did not process this stream --
// i.e. it sent a refused stream or GOAWAY including this stream ID.
func (s *ClientStream) Unprocessed() bool {
return s.unprocessed.Load()
}
func (s *ClientStream) waitOnHeader() {
select {
case <-s.ctx.Done():
// Close the stream to prevent headers/trailers from changing after
// this function returns.
s.Close(ContextErr(s.ctx.Err()))
// headerChan could possibly not be closed yet if closeStream raced
// with operateHeaders; wait until it is closed explicitly here.
<-s.headerChan
case <-s.headerChan:
}
}
// RecvCompress returns the compression algorithm applied to the inbound
// message. It is empty string if there is no compression applied.
func (s *ClientStream) RecvCompress() string {
s.waitOnHeader()
return s.recvCompress
}
// Done returns a channel which is closed when it receives the final status
// from the server.
func (s *ClientStream) Done() <-chan struct{} {
return s.done
}
// Header returns the header metadata of the stream. Acquires the key-value
// pairs of header metadata once it is available. It blocks until i) the
// metadata is ready or ii) there is no header metadata or iii) the stream is
// canceled/expired.
func (s *ClientStream) Header() (metadata.MD, error) {
s.waitOnHeader()
if !s.headerValid || s.noHeaders {
return nil, s.status.Err()
}
return s.header.Copy(), nil
}
// TrailersOnly blocks until a header or trailers-only frame is received and
// then returns true if the stream was trailers-only. If the stream ends
// before headers are received, returns true, nil.
func (s *ClientStream) TrailersOnly() bool {
s.waitOnHeader()
return s.noHeaders
}
// Status returns the status received from the server.
// Status can be read safely only after the stream has ended,
// that is, after Done() is closed.
func (s *ClientStream) Status() *status.Status {
return s.status
}

View File

@ -92,14 +92,11 @@ func (f *trInFlow) newLimit(n uint32) uint32 {
func (f *trInFlow) onData(n uint32) uint32 {
f.unacked += n
if f.unacked >= f.limit/4 {
w := f.unacked
f.unacked = 0
if f.unacked < f.limit/4 {
f.updateEffectiveWindowSize()
return w
return 0
}
f.updateEffectiveWindowSize()
return 0
return f.reset()
}
func (f *trInFlow) reset() uint32 {

View File

@ -225,7 +225,7 @@ func (ht *serverHandlerTransport) do(fn func()) error {
}
}
func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) error {
func (ht *serverHandlerTransport) writeStatus(s *ServerStream, st *status.Status) error {
ht.writeStatusMu.Lock()
defer ht.writeStatusMu.Unlock()
@ -289,14 +289,14 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
// writePendingHeaders sets common and custom headers on the first
// write call (Write, WriteHeader, or WriteStatus)
func (ht *serverHandlerTransport) writePendingHeaders(s *Stream) {
func (ht *serverHandlerTransport) writePendingHeaders(s *ServerStream) {
ht.writeCommonHeaders(s)
ht.writeCustomHeaders(s)
}
// writeCommonHeaders sets common headers on the first write
// call (Write, WriteHeader, or WriteStatus).
func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) {
func (ht *serverHandlerTransport) writeCommonHeaders(s *ServerStream) {
h := ht.rw.Header()
h["Date"] = nil // suppress Date to make tests happy; TODO: restore
h.Set("Content-Type", ht.contentType)
@ -317,7 +317,7 @@ func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) {
// writeCustomHeaders sets custom headers set on the stream via SetHeader
// on the first write call (Write, WriteHeader, or WriteStatus)
func (ht *serverHandlerTransport) writeCustomHeaders(s *Stream) {
func (ht *serverHandlerTransport) writeCustomHeaders(s *ServerStream) {
h := ht.rw.Header()
s.hdrMu.Lock()
@ -333,7 +333,7 @@ func (ht *serverHandlerTransport) writeCustomHeaders(s *Stream) {
s.hdrMu.Unlock()
}
func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data mem.BufferSlice, _ *Options) error {
func (ht *serverHandlerTransport) write(s *ServerStream, hdr []byte, data mem.BufferSlice, _ *WriteOptions) error {
// Always take a reference because otherwise there is no guarantee the data will
// be available after this function returns. This is what callers to Write
// expect.
@ -357,7 +357,7 @@ func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data mem.BufferSl
return nil
}
func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error {
func (ht *serverHandlerTransport) writeHeader(s *ServerStream, md metadata.MD) error {
if err := s.SetHeader(md); err != nil {
return err
}
@ -385,7 +385,7 @@ func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error {
return err
}
func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream func(*Stream)) {
func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream func(*ServerStream)) {
// With this transport type there will be exactly 1 stream: this HTTP request.
var cancel context.CancelFunc
if ht.timeoutSet {
@ -408,16 +408,18 @@ func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream
ctx = metadata.NewIncomingContext(ctx, ht.headerMD)
req := ht.req
s := &Stream{
id: 0, // irrelevant
ctx: ctx,
requestRead: func(int) {},
s := &ServerStream{
Stream: &Stream{
id: 0, // irrelevant
ctx: ctx,
requestRead: func(int) {},
buf: newRecvBuffer(),
method: req.URL.Path,
recvCompress: req.Header.Get("grpc-encoding"),
contentSubtype: ht.contentSubtype,
},
cancel: cancel,
buf: newRecvBuffer(),
st: ht,
method: req.URL.Path,
recvCompress: req.Header.Get("grpc-encoding"),
contentSubtype: ht.contentSubtype,
headerWireLength: 0, // won't have access to header wire length until golang/go#18997.
}
s.trReader = &transportReader{
@ -471,9 +473,7 @@ func (ht *serverHandlerTransport) runStream() {
}
}
func (ht *serverHandlerTransport) IncrMsgSent() {}
func (ht *serverHandlerTransport) IncrMsgRecv() {}
func (ht *serverHandlerTransport) incrMsgRecv() {}
func (ht *serverHandlerTransport) Drain(string) {
panic("Drain() is not implemented")

View File

@ -123,7 +123,7 @@ type http2Client struct {
mu sync.Mutex // guard the following variables
nextID uint32
state transportState
activeStreams map[uint32]*Stream
activeStreams map[uint32]*ClientStream
// prevGoAway ID records the Last-Stream-ID in the previous GOAway frame.
prevGoAwayID uint32
// goAwayReason records the http2.ErrCode and debug data received with the
@ -199,10 +199,10 @@ func isTemporary(err error) bool {
return true
}
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
// NewHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
// and starts to receive messages on it. Non-nil error returns if construction
// fails.
func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts ConnectOptions, onClose func(GoAwayReason)) (_ *http2Client, err error) {
func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts ConnectOptions, onClose func(GoAwayReason)) (_ ClientTransport, err error) {
scheme := "http"
ctx, cancel := context.WithCancel(ctx)
defer func() {
@ -339,7 +339,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
framer: newFramer(conn, writeBufSize, readBufSize, opts.SharedWriteBuffer, maxHeaderListSize),
fc: &trInFlow{limit: uint32(icwz)},
scheme: scheme,
activeStreams: make(map[uint32]*Stream),
activeStreams: make(map[uint32]*ClientStream),
isSecure: isSecure,
perRPCCreds: perRPCCreds,
kp: kp,
@ -480,17 +480,19 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
return t, nil
}
func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *ClientStream {
// TODO(zhaoq): Handle uint32 overflow of Stream.id.
s := &Stream{
ct: t,
done: make(chan struct{}),
method: callHdr.Method,
sendCompress: callHdr.SendCompress,
buf: newRecvBuffer(),
headerChan: make(chan struct{}),
contentSubtype: callHdr.ContentSubtype,
doneFunc: callHdr.DoneFunc,
s := &ClientStream{
Stream: &Stream{
method: callHdr.Method,
sendCompress: callHdr.SendCompress,
buf: newRecvBuffer(),
contentSubtype: callHdr.ContentSubtype,
},
ct: t,
done: make(chan struct{}),
headerChan: make(chan struct{}),
doneFunc: callHdr.DoneFunc,
}
s.wq = newWriteQuota(defaultWriteQuota, s.done)
s.requestRead = func(n int) {
@ -506,7 +508,7 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
ctxDone: s.ctx.Done(),
recv: s.buf,
closeStream: func(err error) {
t.CloseStream(s, err)
s.Close(err)
},
},
windowHandler: func(n int) {
@ -597,12 +599,6 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
for k, v := range callAuthData {
headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
}
if b := stats.OutgoingTags(ctx); b != nil {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-tags-bin", Value: encodeBinHeader(b)})
}
if b := stats.OutgoingTrace(ctx); b != nil {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-trace-bin", Value: encodeBinHeader(b)})
}
if md, added, ok := metadataFromOutgoingContextRaw(ctx); ok {
var k string
@ -738,7 +734,7 @@ func (e NewStreamError) Error() string {
// NewStream creates a stream and registers it into the transport as "active"
// streams. All non-nil errors returned will be *NewStreamError.
func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, error) {
func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*ClientStream, error) {
ctx = peer.NewContext(ctx, t.getPeer())
// ServerName field of the resolver returned address takes precedence over
@ -763,7 +759,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream,
return
}
// The stream was unprocessed by the server.
atomic.StoreUint32(&s.unprocessed, 1)
s.unprocessed.Store(true)
s.write(recvMsg{err: err})
close(s.done)
// If headerChan isn't closed, then close it.
@ -908,21 +904,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*Stream,
return s, nil
}
// CloseStream clears the footprint of a stream when the stream is not needed any more.
// This must not be executed in reader's goroutine.
func (t *http2Client) CloseStream(s *Stream, err error) {
var (
rst bool
rstCode http2.ErrCode
)
if err != nil {
rst = true
rstCode = http2.ErrCodeCancel
}
t.closeStream(s, err, rst, rstCode, status.Convert(err), nil, false)
}
func (t *http2Client) closeStream(s *Stream, err error, rst bool, rstCode http2.ErrCode, st *status.Status, mdata map[string][]string, eosReceived bool) {
func (t *http2Client) closeStream(s *ClientStream, err error, rst bool, rstCode http2.ErrCode, st *status.Status, mdata map[string][]string, eosReceived bool) {
// Set stream status to done.
if s.swapState(streamDone) == streamDone {
// If it was already done, return. If multiple closeStream calls
@ -1085,7 +1067,7 @@ func (t *http2Client) GracefulClose() {
// Write formats the data into HTTP2 data frame(s) and sends it out. The caller
// should proceed only if Write returns nil.
func (t *http2Client) Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *Options) error {
func (t *http2Client) write(s *ClientStream, hdr []byte, data mem.BufferSlice, opts *WriteOptions) error {
reader := data.Reader()
if opts.Last {
@ -1114,10 +1096,11 @@ func (t *http2Client) Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *O
_ = reader.Close()
return err
}
t.incrMsgSent()
return nil
}
func (t *http2Client) getStream(f http2.Frame) *Stream {
func (t *http2Client) getStream(f http2.Frame) *ClientStream {
t.mu.Lock()
s := t.activeStreams[f.Header().StreamID]
t.mu.Unlock()
@ -1127,7 +1110,7 @@ func (t *http2Client) getStream(f http2.Frame) *Stream {
// adjustWindow sends out extra window update over the initial window size
// of stream if the application is requesting data larger in size than
// the window.
func (t *http2Client) adjustWindow(s *Stream, n uint32) {
func (t *http2Client) adjustWindow(s *ClientStream, n uint32) {
if w := s.fc.maybeAdjust(n); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, increment: w})
}
@ -1136,7 +1119,7 @@ func (t *http2Client) adjustWindow(s *Stream, n uint32) {
// updateWindow adjusts the inbound quota for the stream.
// Window updates will be sent out when the cumulative quota
// exceeds the corresponding threshold.
func (t *http2Client) updateWindow(s *Stream, n uint32) {
func (t *http2Client) updateWindow(s *ClientStream, n uint32) {
if w := s.fc.onRead(n); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, increment: w})
}
@ -1242,7 +1225,7 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) {
}
if f.ErrCode == http2.ErrCodeRefusedStream {
// The stream was unprocessed by the server.
atomic.StoreUint32(&s.unprocessed, 1)
s.unprocessed.Store(true)
}
statusCode, ok := http2ErrConvTab[f.ErrCode]
if !ok {
@ -1383,11 +1366,11 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) error {
return connectionErrorf(true, nil, "received goaway and there are no active streams")
}
streamsToClose := make([]*Stream, 0)
streamsToClose := make([]*ClientStream, 0)
for streamID, stream := range t.activeStreams {
if streamID > id && streamID <= upperLimit {
// The stream was unprocessed by the server.
atomic.StoreUint32(&stream.unprocessed, 1)
stream.unprocessed.Store(true)
streamsToClose = append(streamsToClose, stream)
}
}
@ -1439,7 +1422,7 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
return
}
endStream := frame.StreamEnded()
atomic.StoreUint32(&s.bytesReceived, 1)
s.bytesReceived.Store(true)
initialHeader := atomic.LoadUint32(&s.headerChanClosed) == 0
if !initialHeader && !endStream {
@ -1809,14 +1792,18 @@ func (t *http2Client) socketMetrics() *channelz.EphemeralSocketMetrics {
func (t *http2Client) RemoteAddr() net.Addr { return t.remoteAddr }
func (t *http2Client) IncrMsgSent() {
t.channelz.SocketMetrics.MessagesSent.Add(1)
t.channelz.SocketMetrics.LastMessageSentTimestamp.Store(time.Now().UnixNano())
func (t *http2Client) incrMsgSent() {
if channelz.IsOn() {
t.channelz.SocketMetrics.MessagesSent.Add(1)
t.channelz.SocketMetrics.LastMessageSentTimestamp.Store(time.Now().UnixNano())
}
}
func (t *http2Client) IncrMsgRecv() {
t.channelz.SocketMetrics.MessagesReceived.Add(1)
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Store(time.Now().UnixNano())
func (t *http2Client) incrMsgRecv() {
if channelz.IsOn() {
t.channelz.SocketMetrics.MessagesReceived.Add(1)
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Store(time.Now().UnixNano())
}
}
func (t *http2Client) getOutFlowWindow() int64 {

View File

@ -25,7 +25,7 @@ import (
"fmt"
"io"
"math"
"math/rand"
rand "math/rand/v2"
"net"
"net/http"
"strconv"
@ -111,7 +111,7 @@ type http2Server struct {
// already initialized since draining is already underway.
drainEvent *grpcsync.Event
state transportState
activeStreams map[uint32]*Stream
activeStreams map[uint32]*ServerStream
// idle is the time instant when the connection went idle.
// This is either the beginning of the connection or when the number of
// RPCs go down to 0.
@ -256,7 +256,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
inTapHandle: config.InTapHandle,
fc: &trInFlow{limit: uint32(icwz)},
state: reachable,
activeStreams: make(map[uint32]*Stream),
activeStreams: make(map[uint32]*ServerStream),
stats: config.StatsHandlers,
kp: kp,
idle: time.Now(),
@ -359,7 +359,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
// operateHeaders takes action on the decoded headers. Returns an error if fatal
// error encountered and transport needs to close, otherwise returns nil.
func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeadersFrame, handle func(*Stream)) error {
func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeadersFrame, handle func(*ServerStream)) error {
// Acquire max stream ID lock for entire duration
t.maxStreamMu.Lock()
defer t.maxStreamMu.Unlock()
@ -385,11 +385,13 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
t.maxStreamID = streamID
buf := newRecvBuffer()
s := &Stream{
id: streamID,
s := &ServerStream{
Stream: &Stream{
id: streamID,
buf: buf,
fc: &inFlow{limit: uint32(t.initialWindowSize)},
},
st: t,
buf: buf,
fc: &inFlow{limit: uint32(t.initialWindowSize)},
headerWireLength: int(frame.Header().Length),
}
var (
@ -537,12 +539,6 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
// Attach the received metadata to the context.
if len(mdata) > 0 {
s.ctx = metadata.NewIncomingContext(s.ctx, mdata)
if statsTags := mdata["grpc-tags-bin"]; len(statsTags) > 0 {
s.ctx = stats.SetIncomingTags(s.ctx, []byte(statsTags[len(statsTags)-1]))
}
if statsTrace := mdata["grpc-trace-bin"]; len(statsTrace) > 0 {
s.ctx = stats.SetIncomingTrace(s.ctx, []byte(statsTrace[len(statsTrace)-1]))
}
}
t.mu.Lock()
if t.state != reachable {
@ -634,7 +630,7 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
// HandleStreams receives incoming streams using the given handler. This is
// typically run in a separate goroutine.
// traceCtx attaches trace to ctx and returns the new context.
func (t *http2Server) HandleStreams(ctx context.Context, handle func(*Stream)) {
func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStream)) {
defer func() {
close(t.readerDone)
<-t.loopyWriterDone
@ -698,7 +694,7 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*Stream)) {
}
}
func (t *http2Server) getStream(f http2.Frame) (*Stream, bool) {
func (t *http2Server) getStream(f http2.Frame) (*ServerStream, bool) {
t.mu.Lock()
defer t.mu.Unlock()
if t.activeStreams == nil {
@ -716,7 +712,7 @@ func (t *http2Server) getStream(f http2.Frame) (*Stream, bool) {
// adjustWindow sends out extra window update over the initial window size
// of stream if the application is requesting data larger in size than
// the window.
func (t *http2Server) adjustWindow(s *Stream, n uint32) {
func (t *http2Server) adjustWindow(s *ServerStream, n uint32) {
if w := s.fc.maybeAdjust(n); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id, increment: w})
}
@ -726,7 +722,7 @@ func (t *http2Server) adjustWindow(s *Stream, n uint32) {
// updateWindow adjusts the inbound quota for the stream and the transport.
// Window updates will deliver to the controller for sending when
// the cumulative quota exceeds the corresponding threshold.
func (t *http2Server) updateWindow(s *Stream, n uint32) {
func (t *http2Server) updateWindow(s *ServerStream, n uint32) {
if w := s.fc.onRead(n); w > 0 {
t.controlBuf.put(&outgoingWindowUpdate{streamID: s.id,
increment: w,
@ -963,7 +959,7 @@ func (t *http2Server) checkForHeaderListSize(it any) bool {
return true
}
func (t *http2Server) streamContextErr(s *Stream) error {
func (t *http2Server) streamContextErr(s *ServerStream) error {
select {
case <-t.done:
return ErrConnClosing
@ -973,7 +969,7 @@ func (t *http2Server) streamContextErr(s *Stream) error {
}
// WriteHeader sends the header metadata md back to the client.
func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error {
func (t *http2Server) writeHeader(s *ServerStream, md metadata.MD) error {
s.hdrMu.Lock()
defer s.hdrMu.Unlock()
if s.getState() == streamDone {
@ -1006,7 +1002,7 @@ func (t *http2Server) setResetPingStrikes() {
atomic.StoreUint32(&t.resetPingStrikes, 1)
}
func (t *http2Server) writeHeaderLocked(s *Stream) error {
func (t *http2Server) writeHeaderLocked(s *ServerStream) error {
// TODO(mmukhi): Benchmark if the performance gets better if count the metadata and other header fields
// first and create a slice of that exact size.
headerFields := make([]hpack.HeaderField, 0, 2) // at least :status, content-type will be there if none else.
@ -1046,7 +1042,7 @@ func (t *http2Server) writeHeaderLocked(s *Stream) error {
// 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.
func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
func (t *http2Server) writeStatus(s *ServerStream, st *status.Status) error {
s.hdrMu.Lock()
defer s.hdrMu.Unlock()
@ -1117,11 +1113,11 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
// Write converts the data into HTTP2 data frame and sends it out. Non-nil error
// is returns if it fails (e.g., framing error, transport error).
func (t *http2Server) Write(s *Stream, hdr []byte, data mem.BufferSlice, _ *Options) error {
func (t *http2Server) write(s *ServerStream, hdr []byte, data mem.BufferSlice, _ *WriteOptions) error {
reader := data.Reader()
if !s.isHeaderSent() { // Headers haven't been written yet.
if err := t.WriteHeader(s, nil); err != nil {
if err := t.writeHeader(s, nil); err != nil {
_ = reader.Close()
return err
}
@ -1147,6 +1143,7 @@ func (t *http2Server) Write(s *Stream, hdr []byte, data mem.BufferSlice, _ *Opti
_ = reader.Close()
return err
}
t.incrMsgSent()
return nil
}
@ -1276,7 +1273,7 @@ func (t *http2Server) Close(err error) {
}
// deleteStream deletes the stream s from transport's active streams.
func (t *http2Server) deleteStream(s *Stream, eosReceived bool) {
func (t *http2Server) deleteStream(s *ServerStream, eosReceived bool) {
t.mu.Lock()
if _, ok := t.activeStreams[s.id]; ok {
@ -1297,7 +1294,7 @@ func (t *http2Server) deleteStream(s *Stream, eosReceived bool) {
}
// finishStream closes the stream and puts the trailing headerFrame into controlbuf.
func (t *http2Server) finishStream(s *Stream, rst bool, rstCode http2.ErrCode, hdr *headerFrame, eosReceived bool) {
func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrCode, hdr *headerFrame, eosReceived bool) {
// In case stream sending and receiving are invoked in separate
// goroutines (e.g., bi-directional streaming), cancel needs to be
// called to interrupt the potential blocking on other goroutines.
@ -1321,7 +1318,7 @@ func (t *http2Server) finishStream(s *Stream, rst bool, rstCode http2.ErrCode, h
}
// closeStream clears the footprint of a stream when the stream is not needed any more.
func (t *http2Server) closeStream(s *Stream, rst bool, rstCode http2.ErrCode, eosReceived bool) {
func (t *http2Server) closeStream(s *ServerStream, rst bool, rstCode http2.ErrCode, eosReceived bool) {
// In case stream sending and receiving are invoked in separate
// goroutines (e.g., bi-directional streaming), cancel needs to be
// called to interrupt the potential blocking on other goroutines.
@ -1415,14 +1412,18 @@ func (t *http2Server) socketMetrics() *channelz.EphemeralSocketMetrics {
}
}
func (t *http2Server) IncrMsgSent() {
t.channelz.SocketMetrics.MessagesSent.Add(1)
t.channelz.SocketMetrics.LastMessageSentTimestamp.Add(1)
func (t *http2Server) incrMsgSent() {
if channelz.IsOn() {
t.channelz.SocketMetrics.MessagesSent.Add(1)
t.channelz.SocketMetrics.LastMessageSentTimestamp.Add(1)
}
}
func (t *http2Server) IncrMsgRecv() {
t.channelz.SocketMetrics.MessagesReceived.Add(1)
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Add(1)
func (t *http2Server) incrMsgRecv() {
if channelz.IsOn() {
t.channelz.SocketMetrics.MessagesReceived.Add(1)
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Add(1)
}
}
func (t *http2Server) getOutFlowWindow() int64 {
@ -1455,7 +1456,7 @@ func getJitter(v time.Duration) time.Duration {
}
// Generate a jitter between +/- 10% of the value.
r := int64(v / 10)
j := rand.Int63n(2*r) - r
j := rand.Int64N(2*r) - r
return time.Duration(j)
}

View File

@ -0,0 +1,178 @@
/*
*
* Copyright 2024 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 transport
import (
"context"
"errors"
"strings"
"sync"
"sync/atomic"
"google.golang.org/grpc/mem"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
// ServerStream implements streaming functionality for a gRPC server.
type ServerStream struct {
*Stream // Embed for common stream functionality.
st internalServerTransport
ctxDone <-chan struct{} // closed at the end of stream. Cache of ctx.Done() (for performance)
cancel context.CancelFunc // invoked at the end of stream to cancel ctx.
// Holds compressor names passed in grpc-accept-encoding metadata from the
// client.
clientAdvertisedCompressors string
headerWireLength int
// hdrMu protects outgoing header and trailer metadata.
hdrMu sync.Mutex
header metadata.MD // the outgoing header metadata. Updated by WriteHeader.
headerSent atomic.Bool // atomically set when the headers are sent out.
}
// Read reads an n byte message from the input stream.
func (s *ServerStream) Read(n int) (mem.BufferSlice, error) {
b, err := s.Stream.read(n)
if err == nil {
s.st.incrMsgRecv()
}
return b, err
}
// SendHeader sends the header metadata for the given stream.
func (s *ServerStream) SendHeader(md metadata.MD) error {
return s.st.writeHeader(s, md)
}
// Write writes the hdr and data bytes to the output stream.
func (s *ServerStream) Write(hdr []byte, data mem.BufferSlice, opts *WriteOptions) error {
return s.st.write(s, hdr, data, opts)
}
// WriteStatus sends the status of a stream to the client. WriteStatus is
// the final call made on a stream and always occurs.
func (s *ServerStream) WriteStatus(st *status.Status) error {
return s.st.writeStatus(s, st)
}
// isHeaderSent indicates whether headers have been sent.
func (s *ServerStream) isHeaderSent() bool {
return s.headerSent.Load()
}
// updateHeaderSent updates headerSent and returns true
// if it was already set.
func (s *ServerStream) updateHeaderSent() bool {
return s.headerSent.Swap(true)
}
// RecvCompress returns the compression algorithm applied to the inbound
// message. It is empty string if there is no compression applied.
func (s *ServerStream) RecvCompress() string {
return s.recvCompress
}
// SendCompress returns the send compressor name.
func (s *ServerStream) SendCompress() string {
return s.sendCompress
}
// ContentSubtype returns the content-subtype for a request. For example, a
// content-subtype of "proto" will result in a content-type of
// "application/grpc+proto". This will always be lowercase. See
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
// more details.
func (s *ServerStream) ContentSubtype() string {
return s.contentSubtype
}
// SetSendCompress sets the compression algorithm to the stream.
func (s *ServerStream) SetSendCompress(name string) error {
if s.isHeaderSent() || s.getState() == streamDone {
return errors.New("transport: set send compressor called after headers sent or stream done")
}
s.sendCompress = name
return nil
}
// SetContext sets the context of the stream. This will be deleted once the
// stats handler callouts all move to gRPC layer.
func (s *ServerStream) SetContext(ctx context.Context) {
s.ctx = ctx
}
// ClientAdvertisedCompressors returns the compressor names advertised by the
// client via grpc-accept-encoding header.
func (s *ServerStream) ClientAdvertisedCompressors() []string {
values := strings.Split(s.clientAdvertisedCompressors, ",")
for i, v := range values {
values[i] = strings.TrimSpace(v)
}
return values
}
// Header returns the header metadata of the stream. It returns the out header
// after t.WriteHeader is called. It does not block and must not be called
// until after WriteHeader.
func (s *ServerStream) Header() (metadata.MD, error) {
// Return the header in stream. It will be the out
// header after t.WriteHeader is called.
return s.header.Copy(), nil
}
// HeaderWireLength returns the size of the headers of the stream as received
// from the wire.
func (s *ServerStream) HeaderWireLength() int {
return s.headerWireLength
}
// SetHeader sets the header metadata. This can be called multiple times.
// This should not be called in parallel to other data writes.
func (s *ServerStream) SetHeader(md metadata.MD) error {
if md.Len() == 0 {
return nil
}
if s.isHeaderSent() || s.getState() == streamDone {
return ErrIllegalHeaderWrite
}
s.hdrMu.Lock()
s.header = metadata.Join(s.header, md)
s.hdrMu.Unlock()
return nil
}
// SetTrailer sets the trailer metadata which will be sent with the RPC status
// by the server. This can be called multiple times.
// This should not be called parallel to other data writes.
func (s *ServerStream) SetTrailer(md metadata.MD) error {
if md.Len() == 0 {
return nil
}
if s.getState() == streamDone {
return ErrIllegalHeaderWrite
}
s.hdrMu.Lock()
s.trailer = metadata.Join(s.trailer, md)
s.hdrMu.Unlock()
return nil
}

View File

@ -27,7 +27,6 @@ import (
"fmt"
"io"
"net"
"strings"
"sync"
"sync/atomic"
"time"
@ -39,7 +38,6 @@ import (
"google.golang.org/grpc/mem"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/stats"
"google.golang.org/grpc/status"
"google.golang.org/grpc/tap"
@ -133,7 +131,7 @@ type recvBufferReader struct {
err error
}
func (r *recvBufferReader) ReadHeader(header []byte) (n int, err error) {
func (r *recvBufferReader) ReadMessageHeader(header []byte) (n int, err error) {
if r.err != nil {
return 0, r.err
}
@ -142,9 +140,9 @@ func (r *recvBufferReader) ReadHeader(header []byte) (n int, err error) {
return n, nil
}
if r.closeStream != nil {
n, r.err = r.readHeaderClient(header)
n, r.err = r.readMessageHeaderClient(header)
} else {
n, r.err = r.readHeader(header)
n, r.err = r.readMessageHeader(header)
}
return n, r.err
}
@ -174,12 +172,12 @@ func (r *recvBufferReader) Read(n int) (buf mem.Buffer, err error) {
return buf, r.err
}
func (r *recvBufferReader) readHeader(header []byte) (n int, err error) {
func (r *recvBufferReader) readMessageHeader(header []byte) (n int, err error) {
select {
case <-r.ctxDone:
return 0, ContextErr(r.ctx.Err())
case m := <-r.recv.get():
return r.readHeaderAdditional(m, header)
return r.readMessageHeaderAdditional(m, header)
}
}
@ -192,7 +190,7 @@ func (r *recvBufferReader) read(n int) (buf mem.Buffer, err error) {
}
}
func (r *recvBufferReader) readHeaderClient(header []byte) (n int, err error) {
func (r *recvBufferReader) readMessageHeaderClient(header []byte) (n int, err error) {
// If the context is canceled, then closes the stream with nil metadata.
// closeStream writes its error parameter to r.recv as a recvMsg.
// r.readAdditional acts on that message and returns the necessary error.
@ -213,9 +211,9 @@ func (r *recvBufferReader) readHeaderClient(header []byte) (n int, err error) {
// faster.
r.closeStream(ContextErr(r.ctx.Err()))
m := <-r.recv.get()
return r.readHeaderAdditional(m, header)
return r.readMessageHeaderAdditional(m, header)
case m := <-r.recv.get():
return r.readHeaderAdditional(m, header)
return r.readMessageHeaderAdditional(m, header)
}
}
@ -246,7 +244,7 @@ func (r *recvBufferReader) readClient(n int) (buf mem.Buffer, err error) {
}
}
func (r *recvBufferReader) readHeaderAdditional(m recvMsg, header []byte) (n int, err error) {
func (r *recvBufferReader) readMessageHeaderAdditional(m recvMsg, header []byte) (n int, err error) {
r.recv.load()
if m.err != nil {
if m.buffer != nil {
@ -288,14 +286,8 @@ const (
// Stream represents an RPC in the transport layer.
type Stream struct {
id uint32
st ServerTransport // nil for client side Stream
ct ClientTransport // nil for server side Stream
ctx context.Context // the associated context of the stream
cancel context.CancelFunc // always nil for client side Stream
done chan struct{} // closed at the end of stream to unblock writers. On the client side.
doneFunc func() // invoked at the end of stream on client side.
ctxDone <-chan struct{} // same as done chan but for server side. Cache of ctx.Done() (for performance)
method string // the associated RPC method of the stream
ctx context.Context // the associated context of the stream
method string // the associated RPC method of the stream
recvCompress string
sendCompress string
buf *recvBuffer
@ -303,58 +295,17 @@ type Stream struct {
fc *inFlow
wq *writeQuota
// Holds compressor names passed in grpc-accept-encoding metadata from the
// client. This is empty for the client side stream.
clientAdvertisedCompressors string
// Callback to state application's intentions to read data. This
// is used to adjust flow control, if needed.
requestRead func(int)
headerChan chan struct{} // closed to indicate the end of header metadata.
headerChanClosed uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times.
// headerValid indicates whether a valid header was received. Only
// meaningful after headerChan is closed (always call waitOnHeader() before
// reading its value). Not valid on server side.
headerValid bool
headerWireLength int // Only set on server side.
// hdrMu protects header and trailer metadata on the server-side.
hdrMu sync.Mutex
// On client side, header keeps the received header metadata.
//
// On server side, header keeps the header set by SetHeader(). The complete
// header will merged into this after t.WriteHeader() is called.
header metadata.MD
trailer metadata.MD // the key-value map of trailer metadata.
noHeaders bool // set if the client never received headers (set only after the stream is done).
// On the server-side, headerSent is atomically set to 1 when the headers are sent out.
headerSent uint32
state streamState
// On client-side it is the status error received from the server.
// On server-side it is unused.
status *status.Status
bytesReceived uint32 // indicates whether any bytes have been received on this stream
unprocessed uint32 // set if the server sends a refused stream or GOAWAY including this stream
// contentSubtype is the content-subtype for requests.
// this must be lowercase or the behavior is undefined.
contentSubtype string
}
// isHeaderSent is only valid on the server-side.
func (s *Stream) isHeaderSent() bool {
return atomic.LoadUint32(&s.headerSent) == 1
}
// updateHeaderSent updates headerSent and returns true
// if it was already set. It is valid only on server-side.
func (s *Stream) updateHeaderSent() bool {
return atomic.SwapUint32(&s.headerSent, 1) == 1
trailer metadata.MD // the key-value map of trailer metadata.
}
func (s *Stream) swapState(st streamState) streamState {
@ -369,110 +320,12 @@ func (s *Stream) getState() streamState {
return streamState(atomic.LoadUint32((*uint32)(&s.state)))
}
func (s *Stream) waitOnHeader() {
if s.headerChan == nil {
// On the server headerChan is always nil since a stream originates
// only after having received headers.
return
}
select {
case <-s.ctx.Done():
// Close the stream to prevent headers/trailers from changing after
// this function returns.
s.ct.CloseStream(s, ContextErr(s.ctx.Err()))
// headerChan could possibly not be closed yet if closeStream raced
// with operateHeaders; wait until it is closed explicitly here.
<-s.headerChan
case <-s.headerChan:
}
}
// RecvCompress returns the compression algorithm applied to the inbound
// message. It is empty string if there is no compression applied.
func (s *Stream) RecvCompress() string {
s.waitOnHeader()
return s.recvCompress
}
// SetSendCompress sets the compression algorithm to the stream.
func (s *Stream) SetSendCompress(name string) error {
if s.isHeaderSent() || s.getState() == streamDone {
return errors.New("transport: set send compressor called after headers sent or stream done")
}
s.sendCompress = name
return nil
}
// SendCompress returns the send compressor name.
func (s *Stream) SendCompress() string {
return s.sendCompress
}
// ClientAdvertisedCompressors returns the compressor names advertised by the
// client via grpc-accept-encoding header.
func (s *Stream) ClientAdvertisedCompressors() []string {
values := strings.Split(s.clientAdvertisedCompressors, ",")
for i, v := range values {
values[i] = strings.TrimSpace(v)
}
return values
}
// Done returns a channel which is closed when it receives the final status
// from the server.
func (s *Stream) Done() <-chan struct{} {
return s.done
}
// Header returns the header metadata of the stream.
//
// On client side, it acquires the key-value pairs of header metadata once it is
// available. It blocks until i) the metadata is ready or ii) there is no header
// metadata or iii) the stream is canceled/expired.
//
// On server side, it returns the out header after t.WriteHeader is called. It
// does not block and must not be called until after WriteHeader.
func (s *Stream) Header() (metadata.MD, error) {
if s.headerChan == nil {
// On server side, return the header in stream. It will be the out
// header after t.WriteHeader is called.
return s.header.Copy(), nil
}
s.waitOnHeader()
if !s.headerValid || s.noHeaders {
return nil, s.status.Err()
}
return s.header.Copy(), nil
}
// TrailersOnly blocks until a header or trailers-only frame is received and
// then returns true if the stream was trailers-only. If the stream ends
// before headers are received, returns true, nil. Client-side only.
func (s *Stream) TrailersOnly() bool {
s.waitOnHeader()
return s.noHeaders
}
// Trailer returns the cached trailer metadata. Note that if it is not called
// after the entire stream is done, it could return an empty MD. Client
// side only.
// after the entire stream is done, it could return an empty MD.
// It can be safely read only after stream has ended that is either read
// or write have returned io.EOF.
func (s *Stream) Trailer() metadata.MD {
c := s.trailer.Copy()
return c
}
// ContentSubtype returns the content-subtype for a request. For example, a
// content-subtype of "proto" will result in a content-type of
// "application/grpc+proto". This will always be lowercase. See
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
// more details.
func (s *Stream) ContentSubtype() string {
return s.contentSubtype
return s.trailer.Copy()
}
// Context returns the context of the stream.
@ -480,90 +333,31 @@ func (s *Stream) Context() context.Context {
return s.ctx
}
// SetContext sets the context of the stream. This will be deleted once the
// stats handler callouts all move to gRPC layer.
func (s *Stream) SetContext(ctx context.Context) {
s.ctx = ctx
}
// Method returns the method for the stream.
func (s *Stream) Method() string {
return s.method
}
// Status returns the status received from the server.
// Status can be read safely only after the stream has ended,
// that is, after Done() is closed.
func (s *Stream) Status() *status.Status {
return s.status
}
// HeaderWireLength returns the size of the headers of the stream as received
// from the wire. Valid only on the server.
func (s *Stream) HeaderWireLength() int {
return s.headerWireLength
}
// SetHeader sets the header metadata. This can be called multiple times.
// Server side only.
// This should not be called in parallel to other data writes.
func (s *Stream) SetHeader(md metadata.MD) error {
if md.Len() == 0 {
return nil
}
if s.isHeaderSent() || s.getState() == streamDone {
return ErrIllegalHeaderWrite
}
s.hdrMu.Lock()
s.header = metadata.Join(s.header, md)
s.hdrMu.Unlock()
return nil
}
// SendHeader sends the given header metadata. The given metadata is
// combined with any metadata set by previous calls to SetHeader and
// then written to the transport stream.
func (s *Stream) SendHeader(md metadata.MD) error {
return s.st.WriteHeader(s, md)
}
// SetTrailer sets the trailer metadata which will be sent with the RPC status
// by the server. This can be called multiple times. Server side only.
// This should not be called parallel to other data writes.
func (s *Stream) SetTrailer(md metadata.MD) error {
if md.Len() == 0 {
return nil
}
if s.getState() == streamDone {
return ErrIllegalHeaderWrite
}
s.hdrMu.Lock()
s.trailer = metadata.Join(s.trailer, md)
s.hdrMu.Unlock()
return nil
}
func (s *Stream) write(m recvMsg) {
s.buf.put(m)
}
// ReadHeader reads data into the provided header slice from the stream. It
// first checks if there was an error during a previous read operation and
// ReadMessageHeader reads data into the provided header slice from the stream.
// It first checks if there was an error during a previous read operation and
// returns it if present. It then requests a read operation for the length of
// the header. It continues to read from the stream until the entire header
// slice is filled or an error occurs. If an `io.EOF` error is encountered
// with partially read data, it is converted to `io.ErrUnexpectedEOF` to
// indicate an unexpected end of the stream. The method returns any error
// encountered during the read process or nil if the header was successfully
// read.
func (s *Stream) ReadHeader(header []byte) (err error) {
// slice is filled or an error occurs. If an `io.EOF` error is encountered with
// partially read data, it is converted to `io.ErrUnexpectedEOF` to indicate an
// unexpected end of the stream. The method returns any error encountered during
// the read process or nil if the header was successfully read.
func (s *Stream) ReadMessageHeader(header []byte) (err error) {
// Don't request a read if there was an error earlier
if er := s.trReader.er; er != nil {
return er
}
s.requestRead(len(header))
for len(header) != 0 {
n, err := s.trReader.ReadHeader(header)
n, err := s.trReader.ReadMessageHeader(header)
header = header[n:]
if len(header) == 0 {
err = nil
@ -579,7 +373,7 @@ func (s *Stream) ReadHeader(header []byte) (err error) {
}
// Read reads n bytes from the wire for this stream.
func (s *Stream) Read(n int) (data mem.BufferSlice, err error) {
func (s *Stream) read(n int) (data mem.BufferSlice, err error) {
// Don't request a read if there was an error earlier
if er := s.trReader.er; er != nil {
return nil, er
@ -619,8 +413,8 @@ type transportReader struct {
er error
}
func (t *transportReader) ReadHeader(header []byte) (int, error) {
n, err := t.reader.ReadHeader(header)
func (t *transportReader) ReadMessageHeader(header []byte) (int, error) {
n, err := t.reader.ReadMessageHeader(header)
if err != nil {
t.er = err
return 0, err
@ -639,17 +433,6 @@ func (t *transportReader) Read(n int) (mem.Buffer, error) {
return buf, nil
}
// BytesReceived indicates whether any bytes have been received on this stream.
func (s *Stream) BytesReceived() bool {
return atomic.LoadUint32(&s.bytesReceived) == 1
}
// Unprocessed indicates whether the server did not process this stream --
// i.e. it sent a refused stream or GOAWAY including this stream ID.
func (s *Stream) Unprocessed() bool {
return atomic.LoadUint32(&s.unprocessed) == 1
}
// GoString is implemented by Stream so context.String() won't
// race when printing %#v.
func (s *Stream) GoString() string {
@ -725,15 +508,9 @@ type ConnectOptions struct {
BufferPool mem.BufferPool
}
// NewClientTransport establishes the transport with the required ConnectOptions
// and returns it to the caller.
func NewClientTransport(connectCtx, ctx context.Context, addr resolver.Address, opts ConnectOptions, onClose func(GoAwayReason)) (ClientTransport, error) {
return newHTTP2Client(connectCtx, ctx, addr, opts, onClose)
}
// Options provides additional hints and information for message
// WriteOptions provides additional hints and information for message
// transmission.
type Options struct {
type WriteOptions struct {
// Last indicates whether this write is the last piece for
// this stream.
Last bool
@ -782,18 +559,8 @@ type ClientTransport interface {
// It does not block.
GracefulClose()
// Write sends the data for the given stream. A nil stream indicates
// the write is to be performed on the transport as a whole.
Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *Options) error
// NewStream creates a Stream for an RPC.
NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, error)
// CloseStream clears the footprint of a stream when the stream is
// not needed any more. The err indicates the error incurred when
// CloseStream is called. Must be called when a stream is finished
// unless the associated transport is closing.
CloseStream(stream *Stream, err error)
NewStream(ctx context.Context, callHdr *CallHdr) (*ClientStream, error)
// Error returns a channel that is closed when some I/O error
// happens. Typically the caller should have a goroutine to monitor
@ -813,12 +580,6 @@ type ClientTransport interface {
// RemoteAddr returns the remote network address.
RemoteAddr() net.Addr
// IncrMsgSent increments the number of message sent through this transport.
IncrMsgSent()
// IncrMsgRecv increments the number of message received through this transport.
IncrMsgRecv()
}
// ServerTransport is the common interface for all gRPC server-side transport
@ -828,19 +589,7 @@ type ClientTransport interface {
// Write methods for a given Stream will be called serially.
type ServerTransport interface {
// HandleStreams receives incoming streams using the given handler.
HandleStreams(context.Context, func(*Stream))
// WriteHeader sends the header metadata for the given stream.
// WriteHeader may not be called on all streams.
WriteHeader(s *Stream, md metadata.MD) error
// Write sends the data for the given stream.
// Write may not be called on all streams.
Write(s *Stream, hdr []byte, data mem.BufferSlice, opts *Options) error
// WriteStatus sends the status of a stream to the client. WriteStatus is
// the final call made on a stream and always occurs.
WriteStatus(s *Stream, st *status.Status) error
HandleStreams(context.Context, func(*ServerStream))
// Close tears down the transport. Once it is called, the transport
// should not be accessed any more. All the pending streams and their
@ -852,12 +601,14 @@ type ServerTransport interface {
// Drain notifies the client this ServerTransport stops accepting new RPCs.
Drain(debugData string)
}
// IncrMsgSent increments the number of message sent through this transport.
IncrMsgSent()
// IncrMsgRecv increments the number of message received through this transport.
IncrMsgRecv()
type internalServerTransport interface {
ServerTransport
writeHeader(s *ServerStream, md metadata.MD) error
write(s *ServerStream, hdr []byte, data mem.BufferSlice, opts *WriteOptions) error
writeStatus(s *ServerStream, st *status.Status) error
incrMsgRecv()
}
// connectionErrorf creates an ConnectionError with the specified error description.

View File

@ -22,6 +22,11 @@ import (
"io"
)
const (
// 32 KiB is what io.Copy uses.
readAllBufSize = 32 * 1024
)
// BufferSlice offers a means to represent data that spans one or more Buffer
// instances. A BufferSlice is meant to be immutable after creation, and methods
// like Ref create and return copies of the slice. This is why all methods have
@ -219,8 +224,58 @@ func (w *writer) Write(p []byte) (n int, err error) {
// NewWriter wraps the given BufferSlice and BufferPool to implement the
// io.Writer interface. Every call to Write copies the contents of the given
// buffer into a new Buffer pulled from the given pool and the Buffer is added to
// the given BufferSlice.
// buffer into a new Buffer pulled from the given pool and the Buffer is
// added to the given BufferSlice.
func NewWriter(buffers *BufferSlice, pool BufferPool) io.Writer {
return &writer{buffers: buffers, pool: pool}
}
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
//
// Important: A failed call returns a non-nil error and may also return
// partially read buffers. It is the responsibility of the caller to free the
// BufferSlice returned, or its memory will not be reused.
func ReadAll(r io.Reader, pool BufferPool) (BufferSlice, error) {
var result BufferSlice
if wt, ok := r.(io.WriterTo); ok {
// This is more optimal since wt knows the size of chunks it wants to
// write and, hence, we can allocate buffers of an optimal size to fit
// them. E.g. might be a single big chunk, and we wouldn't chop it
// into pieces.
w := NewWriter(&result, pool)
_, err := wt.WriteTo(w)
return result, err
}
nextBuffer:
for {
buf := pool.Get(readAllBufSize)
// We asked for 32KiB but may have been given a bigger buffer.
// Use all of it if that's the case.
*buf = (*buf)[:cap(*buf)]
usedCap := 0
for {
n, err := r.Read((*buf)[usedCap:])
usedCap += n
if err != nil {
if usedCap == 0 {
// Nothing in this buf, put it back
pool.Put(buf)
} else {
*buf = (*buf)[:usedCap]
result = append(result, NewBuffer(buf, pool))
}
if err == io.EOF {
err = nil
}
return result, err
}
if len(*buf) == usedCap {
result = append(result, NewBuffer(buf, pool))
continue nextBuffer
}
}
}
}

View File

@ -62,7 +62,7 @@ func (p *PreparedMsg) Encode(s Stream, msg any) error {
materializedData := data.Materialize()
data.Free()
p.encodedData = mem.BufferSlice{mem.NewBuffer(&materializedData, nil)}
p.encodedData = mem.BufferSlice{mem.SliceBuffer(materializedData)}
// TODO: it should be possible to grab the bufferPool from the underlying
// stream implementation with a type cast to its actual type (such as
@ -76,7 +76,7 @@ func (p *PreparedMsg) Encode(s Stream, msg any) error {
if p.pf.isCompressed() {
materializedCompData := compData.Materialize()
compData.Free()
compData = mem.BufferSlice{mem.NewBuffer(&materializedCompData, nil)}
compData = mem.BufferSlice{mem.SliceBuffer(materializedCompData)}
}
p.hdr, p.payload = msgHeader(p.encodedData, compData, p.pf)

View File

@ -22,6 +22,7 @@ package resolver
import (
"context"
"errors"
"fmt"
"net"
"net/url"
@ -237,8 +238,8 @@ type ClientConn interface {
// UpdateState can be omitted.
UpdateState(State) error
// ReportError notifies the ClientConn that the Resolver encountered an
// error. The ClientConn will notify the load balancer and begin calling
// ResolveNow on the Resolver with exponential backoff.
// error. The ClientConn then forwards this error to the load balancing
// policy.
ReportError(error)
// NewAddress is called by resolver to notify ClientConn a new list
// of resolved addresses.
@ -330,3 +331,20 @@ type AuthorityOverrider interface {
// typically in line, and must keep it unchanged.
OverrideAuthority(Target) string
}
// ValidateEndpoints validates endpoints from a petiole policy's perspective.
// Petiole policies should call this before calling into their children. See
// [gRPC A61](https://github.com/grpc/proposal/blob/master/A61-IPv4-IPv6-dualstack-backends.md)
// for details.
func ValidateEndpoints(endpoints []Endpoint) error {
if len(endpoints) == 0 {
return errors.New("endpoints list is empty")
}
for _, endpoint := range endpoints {
for range endpoint.Addresses {
return nil
}
}
return errors.New("endpoints list contains no addresses")
}

View File

@ -622,7 +622,7 @@ func (pf payloadFormat) isCompressed() bool {
}
type streamReader interface {
ReadHeader(header []byte) error
ReadMessageHeader(header []byte) error
Read(n int) (mem.BufferSlice, error)
}
@ -656,7 +656,7 @@ type parser struct {
// that the underlying streamReader must not return an incompatible
// error.
func (p *parser) recvMsg(maxReceiveMessageSize int) (payloadFormat, mem.BufferSlice, error) {
err := p.r.ReadHeader(p.header[:])
err := p.r.ReadMessageHeader(p.header[:])
if err != nil {
return 0, nil, err
}
@ -664,9 +664,6 @@ func (p *parser) recvMsg(maxReceiveMessageSize int) (payloadFormat, mem.BufferSl
pf := payloadFormat(p.header[0])
length := binary.BigEndian.Uint32(p.header[1:])
if length == 0 {
return pf, nil, nil
}
if int64(length) > int64(maxInt) {
return 0, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message larger than max length allowed on current machine (%d vs. %d)", length, maxInt)
}
@ -817,7 +814,7 @@ func (p *payloadInfo) free() {
// the buffer is no longer needed.
// TODO: Refactor this function to reduce the number of arguments.
// See: https://google.github.io/styleguide/go/best-practices.html#function-argument-lists
func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, isServer bool,
func recvAndDecompress(p *parser, s recvCompressor, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, isServer bool,
) (out mem.BufferSlice, err error) {
pf, compressed, err := p.recvMsg(maxReceiveMessageSize)
if err != nil {
@ -841,7 +838,7 @@ func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxRecei
var uncompressedBuf []byte
uncompressedBuf, err = dc.Do(compressed.Reader())
if err == nil {
out = mem.BufferSlice{mem.NewBuffer(&uncompressedBuf, nil)}
out = mem.BufferSlice{mem.SliceBuffer(uncompressedBuf)}
}
size = len(uncompressedBuf)
} else {
@ -877,30 +874,7 @@ func decompress(compressor encoding.Compressor, d mem.BufferSlice, maxReceiveMes
return nil, 0, err
}
// TODO: Can/should this still be preserved with the new BufferSlice API? Are
// there any actual benefits to allocating a single large buffer instead of
// multiple smaller ones?
//if sizer, ok := compressor.(interface {
// DecompressedSize(compressedBytes []byte) int
//}); ok {
// if size := sizer.DecompressedSize(d); size >= 0 {
// if size > maxReceiveMessageSize {
// return nil, size, nil
// }
// // size is used as an estimate to size the buffer, but we
// // will read more data if available.
// // +MinRead so ReadFrom will not reallocate if size is correct.
// //
// // TODO: If we ensure that the buffer size is the same as the DecompressedSize,
// // we can also utilize the recv buffer pool here.
// buf := bytes.NewBuffer(make([]byte, 0, size+bytes.MinRead))
// bytesRead, err := buf.ReadFrom(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1))
// return buf.Bytes(), int(bytesRead), err
// }
//}
var out mem.BufferSlice
_, err = io.Copy(mem.NewWriter(&out, pool), io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1))
out, err := mem.ReadAll(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1), pool)
if err != nil {
out.Free()
return nil, 0, err
@ -908,10 +882,14 @@ func decompress(compressor encoding.Compressor, d mem.BufferSlice, maxReceiveMes
return out, out.Len(), nil
}
type recvCompressor interface {
RecvCompress() string
}
// For the two compressor parameters, both should not be set, but if they are,
// dc takes precedence over compressor.
// TODO(dfawley): wrap the old compressor/decompressor using the new API?
func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m any, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, isServer bool) error {
func recv(p *parser, c baseCodec, s recvCompressor, dc Decompressor, m any, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, isServer bool) error {
data, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor, isServer)
if err != nil {
return err

View File

@ -87,12 +87,13 @@ func init() {
var statusOK = status.New(codes.OK, "")
var logger = grpclog.Component("core")
type methodHandler func(srv any, ctx context.Context, dec func(any) error, interceptor UnaryServerInterceptor) (any, error)
// MethodHandler is a function type that processes a unary RPC method call.
type MethodHandler func(srv any, ctx context.Context, dec func(any) error, interceptor UnaryServerInterceptor) (any, error)
// MethodDesc represents an RPC service's method specification.
type MethodDesc struct {
MethodName string
Handler methodHandler
Handler MethodHandler
}
// ServiceDesc represents an RPC service's specification.
@ -621,8 +622,8 @@ func bufferPool(bufferPool mem.BufferPool) ServerOption {
// workload (assuming a QPS of a few thousand requests/sec).
const serverWorkerResetThreshold = 1 << 16
// serverWorker blocks on a *transport.Stream channel forever and waits for
// data to be fed by serveStreams. This allows multiple requests to be
// serverWorker blocks on a *transport.ServerStream channel forever and waits
// for data to be fed by serveStreams. This allows multiple requests to be
// processed by the same goroutine, removing the need for expensive stack
// re-allocations (see the runtime.morestack problem [1]).
//
@ -1020,7 +1021,7 @@ func (s *Server) serveStreams(ctx context.Context, st transport.ServerTransport,
}()
streamQuota := newHandlerQuota(s.opts.maxConcurrentStreams)
st.HandleStreams(ctx, func(stream *transport.Stream) {
st.HandleStreams(ctx, func(stream *transport.ServerStream) {
s.handlersWG.Add(1)
streamQuota.acquire()
f := func() {
@ -1136,7 +1137,7 @@ func (s *Server) incrCallsFailed() {
s.channelz.ServerMetrics.CallsFailed.Add(1)
}
func (s *Server) sendResponse(ctx context.Context, t transport.ServerTransport, stream *transport.Stream, msg any, cp Compressor, opts *transport.Options, comp encoding.Compressor) error {
func (s *Server) sendResponse(ctx context.Context, stream *transport.ServerStream, msg any, cp Compressor, opts *transport.WriteOptions, comp encoding.Compressor) error {
data, err := encode(s.getCodec(stream.ContentSubtype()), msg)
if err != nil {
channelz.Error(logger, s.channelz, "grpc: server failed to encode response: ", err)
@ -1165,7 +1166,7 @@ func (s *Server) sendResponse(ctx context.Context, t transport.ServerTransport,
if payloadLen > s.opts.maxSendMessageSize {
return status.Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max (%d vs. %d)", payloadLen, s.opts.maxSendMessageSize)
}
err = t.Write(stream, hdr, payload, opts)
err = stream.Write(hdr, payload, opts)
if err == nil {
if len(s.opts.statsHandlers) != 0 {
for _, sh := range s.opts.statsHandlers {
@ -1212,7 +1213,7 @@ func getChainUnaryHandler(interceptors []UnaryServerInterceptor, curr int, info
}
}
func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, md *MethodDesc, trInfo *traceInfo) (err error) {
func (s *Server) processUnaryRPC(ctx context.Context, stream *transport.ServerStream, info *serviceInfo, md *MethodDesc, trInfo *traceInfo) (err error) {
shs := s.opts.statsHandlers
if len(shs) != 0 || trInfo != nil || channelz.IsOn() {
if channelz.IsOn() {
@ -1320,7 +1321,7 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor
decomp = encoding.GetCompressor(rc)
if decomp == nil {
st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc)
t.WriteStatus(stream, st)
stream.WriteStatus(st)
return st.Err()
}
}
@ -1354,15 +1355,12 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor
d, err := recvAndDecompress(&parser{r: stream, bufferPool: s.opts.bufferPool}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp, true)
if err != nil {
if e := t.WriteStatus(stream, status.Convert(err)); e != nil {
if e := stream.WriteStatus(status.Convert(err)); e != nil {
channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e)
}
return err
}
defer d.Free()
if channelz.IsOn() {
t.IncrMsgRecv()
}
df := func(v any) error {
if err := s.getCodec(stream.ContentSubtype()).Unmarshal(d, v); err != nil {
return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err)
@ -1404,7 +1402,7 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor
trInfo.tr.LazyLog(stringer(appStatus.Message()), true)
trInfo.tr.SetError()
}
if e := t.WriteStatus(stream, appStatus); e != nil {
if e := stream.WriteStatus(appStatus); e != nil {
channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e)
}
if len(binlogs) != 0 {
@ -1431,20 +1429,20 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor
if trInfo != nil {
trInfo.tr.LazyLog(stringer("OK"), false)
}
opts := &transport.Options{Last: true}
opts := &transport.WriteOptions{Last: true}
// Server handler could have set new compressor by calling SetSendCompressor.
// In case it is set, we need to use it for compressing outbound message.
if stream.SendCompress() != sendCompressorName {
comp = encoding.GetCompressor(stream.SendCompress())
}
if err := s.sendResponse(ctx, t, stream, reply, cp, opts, comp); err != nil {
if err := s.sendResponse(ctx, stream, reply, cp, opts, comp); err != nil {
if err == io.EOF {
// The entire stream is done (for unary RPC only).
return err
}
if sts, ok := status.FromError(err); ok {
if e := t.WriteStatus(stream, sts); e != nil {
if e := stream.WriteStatus(sts); e != nil {
channelz.Warningf(logger, s.channelz, "grpc: Server.processUnaryRPC failed to write status: %v", e)
}
} else {
@ -1484,9 +1482,6 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor
binlog.Log(ctx, sm)
}
}
if channelz.IsOn() {
t.IncrMsgSent()
}
if trInfo != nil {
trInfo.tr.LazyLog(&payload{sent: true, msg: reply}, true)
}
@ -1502,7 +1497,7 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor
binlog.Log(ctx, st)
}
}
return t.WriteStatus(stream, statusOK)
return stream.WriteStatus(statusOK)
}
// chainStreamServerInterceptors chains all stream server interceptors into one.
@ -1541,7 +1536,7 @@ func getChainStreamHandler(interceptors []StreamServerInterceptor, curr int, inf
}
}
func (s *Server) processStreamingRPC(ctx context.Context, t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, sd *StreamDesc, trInfo *traceInfo) (err error) {
func (s *Server) processStreamingRPC(ctx context.Context, stream *transport.ServerStream, info *serviceInfo, sd *StreamDesc, trInfo *traceInfo) (err error) {
if channelz.IsOn() {
s.incrCallsStarted()
}
@ -1561,7 +1556,6 @@ func (s *Server) processStreamingRPC(ctx context.Context, t transport.ServerTran
ctx = NewContextWithServerTransportStream(ctx, stream)
ss := &serverStream{
ctx: ctx,
t: t,
s: stream,
p: &parser{r: stream, bufferPool: s.opts.bufferPool},
codec: s.getCodec(stream.ContentSubtype()),
@ -1648,7 +1642,7 @@ func (s *Server) processStreamingRPC(ctx context.Context, t transport.ServerTran
ss.decomp = encoding.GetCompressor(rc)
if ss.decomp == nil {
st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc)
t.WriteStatus(ss.s, st)
ss.s.WriteStatus(st)
return st.Err()
}
}
@ -1717,7 +1711,7 @@ func (s *Server) processStreamingRPC(ctx context.Context, t transport.ServerTran
binlog.Log(ctx, st)
}
}
t.WriteStatus(ss.s, appStatus)
ss.s.WriteStatus(appStatus)
// TODO: Should we log an error from WriteStatus here and below?
return appErr
}
@ -1735,10 +1729,10 @@ func (s *Server) processStreamingRPC(ctx context.Context, t transport.ServerTran
binlog.Log(ctx, st)
}
}
return t.WriteStatus(ss.s, statusOK)
return ss.s.WriteStatus(statusOK)
}
func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream) {
func (s *Server) handleStream(t transport.ServerTransport, stream *transport.ServerStream) {
ctx := stream.Context()
ctx = contextWithServer(ctx, s)
var ti *traceInfo
@ -1768,7 +1762,7 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
ti.tr.SetError()
}
errDesc := fmt.Sprintf("malformed method name: %q", stream.Method())
if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
if err := stream.WriteStatus(status.New(codes.Unimplemented, errDesc)); err != nil {
if ti != nil {
ti.tr.LazyLog(&fmtStringer{"%v", []any{err}}, true)
ti.tr.SetError()
@ -1783,17 +1777,20 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
service := sm[:pos]
method := sm[pos+1:]
md, _ := metadata.FromIncomingContext(ctx)
for _, sh := range s.opts.statsHandlers {
ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: stream.Method()})
sh.HandleRPC(ctx, &stats.InHeader{
FullMethod: stream.Method(),
RemoteAddr: t.Peer().Addr,
LocalAddr: t.Peer().LocalAddr,
Compression: stream.RecvCompress(),
WireLength: stream.HeaderWireLength(),
Header: md,
})
// FromIncomingContext is expensive: skip if there are no statsHandlers
if len(s.opts.statsHandlers) > 0 {
md, _ := metadata.FromIncomingContext(ctx)
for _, sh := range s.opts.statsHandlers {
ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: stream.Method()})
sh.HandleRPC(ctx, &stats.InHeader{
FullMethod: stream.Method(),
RemoteAddr: t.Peer().Addr,
LocalAddr: t.Peer().LocalAddr,
Compression: stream.RecvCompress(),
WireLength: stream.HeaderWireLength(),
Header: md,
})
}
}
// To have calls in stream callouts work. Will delete once all stats handler
// calls come from the gRPC layer.
@ -1802,17 +1799,17 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
srv, knownService := s.services[service]
if knownService {
if md, ok := srv.methods[method]; ok {
s.processUnaryRPC(ctx, t, stream, srv, md, ti)
s.processUnaryRPC(ctx, stream, srv, md, ti)
return
}
if sd, ok := srv.streams[method]; ok {
s.processStreamingRPC(ctx, t, stream, srv, sd, ti)
s.processStreamingRPC(ctx, stream, srv, sd, ti)
return
}
}
// Unknown service, or known server unknown method.
if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil {
s.processStreamingRPC(ctx, t, stream, nil, unknownDesc, ti)
s.processStreamingRPC(ctx, stream, nil, unknownDesc, ti)
return
}
var errDesc string
@ -1825,7 +1822,7 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
ti.tr.LazyPrintf("%s", errDesc)
ti.tr.SetError()
}
if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
if err := stream.WriteStatus(status.New(codes.Unimplemented, errDesc)); err != nil {
if ti != nil {
ti.tr.LazyLog(&fmtStringer{"%v", []any{err}}, true)
ti.tr.SetError()
@ -2100,7 +2097,7 @@ func SendHeader(ctx context.Context, md metadata.MD) error {
// Notice: This function is EXPERIMENTAL and may be changed or removed in a
// later release.
func SetSendCompressor(ctx context.Context, name string) error {
stream, ok := ServerTransportStreamFromContext(ctx).(*transport.Stream)
stream, ok := ServerTransportStreamFromContext(ctx).(*transport.ServerStream)
if !ok || stream == nil {
return fmt.Errorf("failed to fetch the stream from the given context")
}
@ -2122,7 +2119,7 @@ func SetSendCompressor(ctx context.Context, name string) error {
// Notice: This function is EXPERIMENTAL and may be changed or removed in a
// later release.
func ClientSupportedCompressors(ctx context.Context) ([]string, error) {
stream, ok := ServerTransportStreamFromContext(ctx).(*transport.Stream)
stream, ok := ServerTransportStreamFromContext(ctx).(*transport.ServerStream)
if !ok || stream == nil {
return nil, fmt.Errorf("failed to fetch the stream from the given context %v", ctx)
}

View File

@ -168,6 +168,7 @@ func init() {
return parseServiceConfig(js, defaultMaxCallAttempts)
}
}
func parseServiceConfig(js string, maxAttempts int) *serviceconfig.ParseResult {
if len(js) == 0 {
return &serviceconfig.ParseResult{Err: fmt.Errorf("no JSON service config provided")}
@ -297,7 +298,7 @@ func convertRetryPolicy(jrp *jsonRetryPolicy, maxAttempts int) (p *internalservi
return rp, nil
}
func min(a, b *int) *int {
func minPointers(a, b *int) *int {
if *a < *b {
return a
}
@ -309,7 +310,7 @@ func getMaxSize(mcMax, doptMax *int, defaultVal int) *int {
return &defaultVal
}
if mcMax != nil && doptMax != nil {
return min(mcMax, doptMax)
return minPointers(mcMax, doptMax)
}
if mcMax != nil {
return mcMax

81
vendor/google.golang.org/grpc/stats/metrics.go generated vendored Normal file
View File

@ -0,0 +1,81 @@
/*
* Copyright 2024 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 stats
import "maps"
// MetricSet is a set of metrics to record. Once created, MetricSet is immutable,
// however Add and Remove can make copies with specific metrics added or
// removed, respectively.
//
// Do not construct directly; use NewMetricSet instead.
type MetricSet struct {
// metrics are the set of metrics to initialize.
metrics map[string]bool
}
// NewMetricSet returns a MetricSet containing metricNames.
func NewMetricSet(metricNames ...string) *MetricSet {
newMetrics := make(map[string]bool)
for _, metric := range metricNames {
newMetrics[metric] = true
}
return &MetricSet{metrics: newMetrics}
}
// Metrics returns the metrics set. The returned map is read-only and must not
// be modified.
func (m *MetricSet) Metrics() map[string]bool {
return m.metrics
}
// Add adds the metricNames to the metrics set and returns a new copy with the
// additional metrics.
func (m *MetricSet) Add(metricNames ...string) *MetricSet {
newMetrics := make(map[string]bool)
for metric := range m.metrics {
newMetrics[metric] = true
}
for _, metric := range metricNames {
newMetrics[metric] = true
}
return &MetricSet{metrics: newMetrics}
}
// Join joins the metrics passed in with the metrics set, and returns a new copy
// with the merged metrics.
func (m *MetricSet) Join(metrics *MetricSet) *MetricSet {
newMetrics := make(map[string]bool)
maps.Copy(newMetrics, m.metrics)
maps.Copy(newMetrics, metrics.metrics)
return &MetricSet{metrics: newMetrics}
}
// Remove removes the metricNames from the metrics set and returns a new copy
// with the metrics removed.
func (m *MetricSet) Remove(metricNames ...string) *MetricSet {
newMetrics := make(map[string]bool)
for metric := range m.metrics {
newMetrics[metric] = true
}
for _, metric := range metricNames {
delete(newMetrics, metric)
}
return &MetricSet{metrics: newMetrics}
}

View File

@ -260,84 +260,42 @@ func (s *ConnEnd) IsClient() bool { return s.Client }
func (s *ConnEnd) isConnStats() {}
type incomingTagsKey struct{}
type outgoingTagsKey struct{}
// SetTags attaches stats tagging data to the context, which will be sent in
// the outgoing RPC with the header grpc-tags-bin. Subsequent calls to
// SetTags will overwrite the values from earlier calls.
//
// NOTE: this is provided only for backward compatibility with existing clients
// and will likely be removed in an upcoming release. New uses should transmit
// this type of data using metadata with a different, non-reserved (i.e. does
// not begin with "grpc-") header name.
// Deprecated: set the `grpc-tags-bin` header in the metadata instead.
func SetTags(ctx context.Context, b []byte) context.Context {
return context.WithValue(ctx, outgoingTagsKey{}, b)
return metadata.AppendToOutgoingContext(ctx, "grpc-tags-bin", string(b))
}
// Tags returns the tags from the context for the inbound RPC.
//
// NOTE: this is provided only for backward compatibility with existing clients
// and will likely be removed in an upcoming release. New uses should transmit
// this type of data using metadata with a different, non-reserved (i.e. does
// not begin with "grpc-") header name.
// Deprecated: obtain the `grpc-tags-bin` header from metadata instead.
func Tags(ctx context.Context) []byte {
b, _ := ctx.Value(incomingTagsKey{}).([]byte)
return b
traceValues := metadata.ValueFromIncomingContext(ctx, "grpc-tags-bin")
if len(traceValues) == 0 {
return nil
}
return []byte(traceValues[len(traceValues)-1])
}
// SetIncomingTags attaches stats tagging data to the context, to be read by
// the application (not sent in outgoing RPCs).
//
// This is intended for gRPC-internal use ONLY.
func SetIncomingTags(ctx context.Context, b []byte) context.Context {
return context.WithValue(ctx, incomingTagsKey{}, b)
}
// OutgoingTags returns the tags from the context for the outbound RPC.
//
// This is intended for gRPC-internal use ONLY.
func OutgoingTags(ctx context.Context) []byte {
b, _ := ctx.Value(outgoingTagsKey{}).([]byte)
return b
}
type incomingTraceKey struct{}
type outgoingTraceKey struct{}
// SetTrace attaches stats tagging data to the context, which will be sent in
// the outgoing RPC with the header grpc-trace-bin. Subsequent calls to
// SetTrace will overwrite the values from earlier calls.
//
// NOTE: this is provided only for backward compatibility with existing clients
// and will likely be removed in an upcoming release. New uses should transmit
// this type of data using metadata with a different, non-reserved (i.e. does
// not begin with "grpc-") header name.
// Deprecated: set the `grpc-trace-bin` header in the metadata instead.
func SetTrace(ctx context.Context, b []byte) context.Context {
return context.WithValue(ctx, outgoingTraceKey{}, b)
return metadata.AppendToOutgoingContext(ctx, "grpc-trace-bin", string(b))
}
// Trace returns the trace from the context for the inbound RPC.
//
// NOTE: this is provided only for backward compatibility with existing clients
// and will likely be removed in an upcoming release. New uses should transmit
// this type of data using metadata with a different, non-reserved (i.e. does
// not begin with "grpc-") header name.
// Deprecated: obtain the `grpc-trace-bin` header from metadata instead.
func Trace(ctx context.Context) []byte {
b, _ := ctx.Value(incomingTraceKey{}).([]byte)
return b
}
// SetIncomingTrace attaches stats tagging data to the context, to be read by
// the application (not sent in outgoing RPCs). It is intended for
// gRPC-internal use.
func SetIncomingTrace(ctx context.Context, b []byte) context.Context {
return context.WithValue(ctx, incomingTraceKey{}, b)
}
// OutgoingTrace returns the trace from the context for the outbound RPC. It is
// intended for gRPC-internal use.
func OutgoingTrace(ctx context.Context) []byte {
b, _ := ctx.Value(outgoingTraceKey{}).([]byte)
return b
traceValues := metadata.ValueFromIncomingContext(ctx, "grpc-trace-bin")
if len(traceValues) == 0 {
return nil
}
return []byte(traceValues[len(traceValues)-1])
}

View File

@ -23,7 +23,7 @@ import (
"errors"
"io"
"math"
"math/rand"
rand "math/rand/v2"
"strconv"
"sync"
"time"
@ -113,7 +113,9 @@ type ClientStream interface {
// SendMsg is generally called by generated code. On error, SendMsg aborts
// the stream. If the error was generated by the client, the status is
// returned directly; otherwise, io.EOF is returned and the status of
// the stream may be discovered using RecvMsg.
// the stream may be discovered using RecvMsg. For unary or server-streaming
// RPCs (StreamDesc.ClientStreams is false), a nil error is returned
// unconditionally.
//
// SendMsg blocks until:
// - There is sufficient flow control to schedule m with the transport, or
@ -216,7 +218,7 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
var mc serviceconfig.MethodConfig
var onCommit func()
var newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
newStream := func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, opts...)
}
@ -584,7 +586,7 @@ type csAttempt struct {
ctx context.Context
cs *clientStream
t transport.ClientTransport
s *transport.Stream
s *transport.ClientStream
p *parser
pickResult balancer.PickResult
@ -706,11 +708,10 @@ func (a *csAttempt) shouldRetry(err error) (bool, error) {
cs.numRetriesSincePushback = 0
} else {
fact := math.Pow(rp.BackoffMultiplier, float64(cs.numRetriesSincePushback))
cur := float64(rp.InitialBackoff) * fact
if max := float64(rp.MaxBackoff); cur > max {
cur = max
}
dur = time.Duration(rand.Int63n(int64(cur)))
cur := min(float64(rp.InitialBackoff)*fact, float64(rp.MaxBackoff))
// Apply jitter by multiplying with a random factor between 0.8 and 1.2
cur *= 0.8 + 0.4*rand.Float64()
dur = time.Duration(int64(cur))
cs.numRetriesSincePushback++
}
@ -991,7 +992,7 @@ func (cs *clientStream) CloseSend() error {
}
cs.sentLast = true
op := func(a *csAttempt) error {
a.t.Write(a.s, nil, nil, &transport.Options{Last: true})
a.s.Write(nil, nil, &transport.WriteOptions{Last: true})
// Always return nil; io.EOF is the only error that might make sense
// instead, but there is no need to signal the client to call RecvMsg
// as the only use left for the stream after CloseSend is to call
@ -1083,7 +1084,7 @@ func (a *csAttempt) sendMsg(m any, hdr []byte, payld mem.BufferSlice, dataLength
}
a.mu.Unlock()
}
if err := a.t.Write(a.s, hdr, payld, &transport.Options{Last: !cs.desc.ClientStreams}); err != nil {
if err := a.s.Write(hdr, payld, &transport.WriteOptions{Last: !cs.desc.ClientStreams}); err != nil {
if !cs.desc.ClientStreams {
// For non-client-streaming RPCs, we return nil instead of EOF on error
// because the generated code requires it. finish is not called; RecvMsg()
@ -1097,9 +1098,6 @@ func (a *csAttempt) sendMsg(m any, hdr []byte, payld mem.BufferSlice, dataLength
sh.HandleRPC(a.ctx, outPayload(true, m, dataLength, payloadLength, time.Now()))
}
}
if channelz.IsOn() {
a.t.IncrMsgSent()
}
return nil
}
@ -1153,9 +1151,6 @@ func (a *csAttempt) recvMsg(m any, payInfo *payloadInfo) (err error) {
Length: payInfo.uncompressedBytes.Len(),
})
}
if channelz.IsOn() {
a.t.IncrMsgRecv()
}
if cs.desc.ServerStreams {
// Subsequent messages should be received by subsequent RecvMsg calls.
return nil
@ -1183,7 +1178,7 @@ func (a *csAttempt) finish(err error) {
}
var tr metadata.MD
if a.s != nil {
a.t.CloseStream(a.s, err)
a.s.Close(err)
tr = a.s.Trailer()
}
@ -1340,7 +1335,7 @@ func newNonRetryClientStream(ctx context.Context, desc *StreamDesc, method strin
}
type addrConnStream struct {
s *transport.Stream
s *transport.ClientStream
ac *addrConn
callHdr *transport.CallHdr
cancel context.CancelFunc
@ -1380,7 +1375,7 @@ func (as *addrConnStream) CloseSend() error {
}
as.sentLast = true
as.t.Write(as.s, nil, nil, &transport.Options{Last: true})
as.s.Write(nil, nil, &transport.WriteOptions{Last: true})
// Always return nil; io.EOF is the only error that might make sense
// instead, but there is no need to signal the client to call RecvMsg
// as the only use left for the stream after CloseSend is to call
@ -1430,7 +1425,7 @@ func (as *addrConnStream) SendMsg(m any) (err error) {
return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", payload.Len(), *as.callInfo.maxSendMessageSize)
}
if err := as.t.Write(as.s, hdr, payload, &transport.Options{Last: !as.desc.ClientStreams}); err != nil {
if err := as.s.Write(hdr, payload, &transport.WriteOptions{Last: !as.desc.ClientStreams}); err != nil {
if !as.desc.ClientStreams {
// For non-client-streaming RPCs, we return nil instead of EOF on error
// because the generated code requires it. finish is not called; RecvMsg()
@ -1440,9 +1435,6 @@ func (as *addrConnStream) SendMsg(m any) (err error) {
return io.EOF
}
if channelz.IsOn() {
as.t.IncrMsgSent()
}
return nil
}
@ -1480,9 +1472,6 @@ func (as *addrConnStream) RecvMsg(m any) (err error) {
return toRPCErr(err)
}
if channelz.IsOn() {
as.t.IncrMsgRecv()
}
if as.desc.ServerStreams {
// Subsequent messages should be received by subsequent RecvMsg calls.
return nil
@ -1510,7 +1499,7 @@ func (as *addrConnStream) finish(err error) {
err = nil
}
if as.s != nil {
as.t.CloseStream(as.s, err)
as.s.Close(err)
}
if err != nil {
@ -1577,8 +1566,7 @@ type ServerStream interface {
// serverStream implements a server side Stream.
type serverStream struct {
ctx context.Context
t transport.ServerTransport
s *transport.Stream
s *transport.ServerStream
p *parser
codec baseCodec
@ -1628,7 +1616,7 @@ func (ss *serverStream) SendHeader(md metadata.MD) error {
return status.Error(codes.Internal, err.Error())
}
err = ss.t.WriteHeader(ss.s, md)
err = ss.s.SendHeader(md)
if len(ss.binlogs) != 0 && !ss.serverHeaderBinlogged {
h, _ := ss.s.Header()
sh := &binarylog.ServerHeader{
@ -1668,7 +1656,7 @@ func (ss *serverStream) SendMsg(m any) (err error) {
}
if err != nil && err != io.EOF {
st, _ := status.FromError(toRPCErr(err))
ss.t.WriteStatus(ss.s, st)
ss.s.WriteStatus(st)
// Non-user specified status was sent out. This should be an error
// case (as a server side Cancel maybe).
//
@ -1676,9 +1664,6 @@ func (ss *serverStream) SendMsg(m any) (err error) {
// status from the service handler, we will log that error instead.
// This behavior is similar to an interceptor.
}
if channelz.IsOn() && err == nil {
ss.t.IncrMsgSent()
}
}()
// Server handler could have set new compressor by calling SetSendCompressor.
@ -1710,7 +1695,7 @@ func (ss *serverStream) SendMsg(m any) (err error) {
if payloadLen > ss.maxSendMessageSize {
return status.Errorf(codes.ResourceExhausted, "trying to send message larger than max (%d vs. %d)", payloadLen, ss.maxSendMessageSize)
}
if err := ss.t.Write(ss.s, hdr, payload, &transport.Options{Last: false}); err != nil {
if err := ss.s.Write(hdr, payload, &transport.WriteOptions{Last: false}); err != nil {
return toRPCErr(err)
}
@ -1756,7 +1741,7 @@ func (ss *serverStream) RecvMsg(m any) (err error) {
}
if err != nil && err != io.EOF {
st, _ := status.FromError(toRPCErr(err))
ss.t.WriteStatus(ss.s, st)
ss.s.WriteStatus(st)
// Non-user specified status was sent out. This should be an error
// case (as a server side Cancel maybe).
//
@ -1764,9 +1749,6 @@ func (ss *serverStream) RecvMsg(m any) (err error) {
// status from the service handler, we will log that error instead.
// This behavior is similar to an interceptor.
}
if channelz.IsOn() && err == nil {
ss.t.IncrMsgRecv()
}
}()
var payInfo *payloadInfo
if len(ss.statsHandler) != 0 || len(ss.binlogs) != 0 {

View File

@ -19,4 +19,4 @@
package grpc
// Version is the current grpc version.
const Version = "1.68.0"
const Version = "1.69.2"

View File

@ -1,40 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.13
// +build !go1.13
package errors
import "reflect"
// Is is a copy of Go 1.13's errors.Is for use with older Go versions.
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflect.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if err = unwrap(err); err == nil {
return false
}
}
}
func unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}

View File

@ -1,13 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.13
// +build go1.13
package errors
import "errors"
// Is is errors.Is.
func Is(err, target error) bool { return errors.Is(err, target) }

View File

@ -117,6 +117,9 @@ type (
// GenerateLegacyUnmarshalJSON determines if the plugin generates the
// UnmarshalJSON([]byte) error method for enums.
GenerateLegacyUnmarshalJSON bool
// APILevel controls which API (Open, Hybrid or Opaque) should be used
// for generated code (.pb.go files).
APILevel int
}
)

View File

@ -32,6 +32,10 @@ func unmarshalGoFeature(b []byte, parent EditionFeatures) EditionFeatures {
v, m := protowire.ConsumeVarint(b)
b = b[m:]
parent.GenerateLegacyUnmarshalJSON = protowire.DecodeBool(v)
case genid.GoFeatures_ApiLevel_field_number:
v, m := protowire.ConsumeVarint(b)
b = b[m:]
parent.APILevel = int(v)
case genid.GoFeatures_StripEnumPrefix_field_number:
v, m := protowire.ConsumeVarint(b)
b = b[m:]

View File

@ -21,18 +21,35 @@ const (
// Field names for pb.GoFeatures.
const (
GoFeatures_LegacyUnmarshalJsonEnum_field_name protoreflect.Name = "legacy_unmarshal_json_enum"
GoFeatures_ApiLevel_field_name protoreflect.Name = "api_level"
GoFeatures_StripEnumPrefix_field_name protoreflect.Name = "strip_enum_prefix"
GoFeatures_LegacyUnmarshalJsonEnum_field_fullname protoreflect.FullName = "pb.GoFeatures.legacy_unmarshal_json_enum"
GoFeatures_ApiLevel_field_fullname protoreflect.FullName = "pb.GoFeatures.api_level"
GoFeatures_StripEnumPrefix_field_fullname protoreflect.FullName = "pb.GoFeatures.strip_enum_prefix"
)
// Field numbers for pb.GoFeatures.
const (
GoFeatures_LegacyUnmarshalJsonEnum_field_number protoreflect.FieldNumber = 1
GoFeatures_ApiLevel_field_number protoreflect.FieldNumber = 2
GoFeatures_StripEnumPrefix_field_number protoreflect.FieldNumber = 3
)
// Full and short names for pb.GoFeatures.APILevel.
const (
GoFeatures_APILevel_enum_fullname = "pb.GoFeatures.APILevel"
GoFeatures_APILevel_enum_name = "APILevel"
)
// Enum values for pb.GoFeatures.APILevel.
const (
GoFeatures_API_LEVEL_UNSPECIFIED_enum_value = 0
GoFeatures_API_OPEN_enum_value = 1
GoFeatures_API_HYBRID_enum_value = 2
GoFeatures_API_OPAQUE_enum_value = 3
)
// Full and short names for pb.GoFeatures.StripEnumPrefix.
const (
GoFeatures_StripEnumPrefix_enum_fullname = "pb.GoFeatures.StripEnumPrefix"

View File

@ -0,0 +1,12 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package genid
const (
NoUnkeyedLiteral_goname = "noUnkeyedLiteral"
NoUnkeyedLiteralA_goname = "XXX_NoUnkeyedLiteral"
BuilderSuffix_goname = "_builder"
)

View File

@ -0,0 +1,128 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"strconv"
"sync/atomic"
"unsafe"
"google.golang.org/protobuf/reflect/protoreflect"
)
func (Export) UnmarshalField(msg any, fieldNum int32) {
UnmarshalField(msg.(protoreflect.ProtoMessage).ProtoReflect(), protoreflect.FieldNumber(fieldNum))
}
// Present checks the presence set for a certain field number (zero
// based, ordered by appearance in original proto file). part is
// a pointer to the correct element in the bitmask array, num is the
// field number unaltered. Example (field number 70 -> part =
// &m.XXX_presence[1], num = 70)
func (Export) Present(part *uint32, num uint32) bool {
// This hook will read an unprotected shadow presence set if
// we're unning under the race detector
raceDetectHookPresent(part, num)
return atomic.LoadUint32(part)&(1<<(num%32)) > 0
}
// SetPresent adds a field to the presence set. part is a pointer to
// the relevant element in the array and num is the field number
// unaltered. size is the number of fields in the protocol
// buffer.
func (Export) SetPresent(part *uint32, num uint32, size uint32) {
// This hook will mutate an unprotected shadow presence set if
// we're running under the race detector
raceDetectHookSetPresent(part, num, presenceSize(size))
for {
old := atomic.LoadUint32(part)
if atomic.CompareAndSwapUint32(part, old, old|(1<<(num%32))) {
return
}
}
}
// SetPresentNonAtomic is like SetPresent, but operates non-atomically.
// It is meant for use by builder methods, where the message is known not
// to be accessible yet by other goroutines.
func (Export) SetPresentNonAtomic(part *uint32, num uint32, size uint32) {
// This hook will mutate an unprotected shadow presence set if
// we're running under the race detector
raceDetectHookSetPresent(part, num, presenceSize(size))
*part |= 1 << (num % 32)
}
// ClearPresence removes a field from the presence set. part is a
// pointer to the relevant element in the presence array and num is
// the field number unaltered.
func (Export) ClearPresent(part *uint32, num uint32) {
// This hook will mutate an unprotected shadow presence set if
// we're running under the race detector
raceDetectHookClearPresent(part, num)
for {
old := atomic.LoadUint32(part)
if atomic.CompareAndSwapUint32(part, old, old&^(1<<(num%32))) {
return
}
}
}
// interfaceToPointer takes a pointer to an empty interface whose value is a
// pointer type, and converts it into a "pointer" that points to the same
// target
func interfaceToPointer(i *any) pointer {
return pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]}
}
func (p pointer) atomicGetPointer() pointer {
return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))}
}
func (p pointer) atomicSetPointer(q pointer) {
atomic.StorePointer((*unsafe.Pointer)(p.p), q.p)
}
// AtomicCheckPointerIsNil takes an interface (which is a pointer to a
// pointer) and returns true if the pointed-to pointer is nil (using an
// atomic load). This function is inlineable and, on x86, just becomes a
// simple load and compare.
func (Export) AtomicCheckPointerIsNil(ptr any) bool {
return interfaceToPointer(&ptr).atomicGetPointer().IsNil()
}
// AtomicSetPointer takes two interfaces (first is a pointer to a pointer,
// second is a pointer) and atomically sets the second pointer into location
// referenced by first pointer. Unfortunately, atomicSetPointer() does not inline
// (even on x86), so this does not become a simple store on x86.
func (Export) AtomicSetPointer(dstPtr, valPtr any) {
interfaceToPointer(&dstPtr).atomicSetPointer(interfaceToPointer(&valPtr))
}
// AtomicLoadPointer loads the pointer at the location pointed at by src,
// and stores that pointer value into the location pointed at by dst.
func (Export) AtomicLoadPointer(ptr Pointer, dst Pointer) {
*(*unsafe.Pointer)(unsafe.Pointer(dst)) = atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(ptr)))
}
// AtomicInitializePointer makes ptr and dst point to the same value.
//
// If *ptr is a nil pointer, it sets *ptr = *dst.
//
// If *ptr is a non-nil pointer, it sets *dst = *ptr.
func (Export) AtomicInitializePointer(ptr Pointer, dst Pointer) {
if !atomic.CompareAndSwapPointer((*unsafe.Pointer)(ptr), unsafe.Pointer(nil), *(*unsafe.Pointer)(dst)) {
*(*unsafe.Pointer)(unsafe.Pointer(dst)) = atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(ptr)))
}
}
// MessageFieldStringOf returns the field formatted as a string,
// either as the field name if resolvable otherwise as a decimal string.
func (Export) MessageFieldStringOf(md protoreflect.MessageDescriptor, n protoreflect.FieldNumber) string {
fd := md.Fields().ByNumber(n)
if fd != nil {
return string(fd.Name())
}
return strconv.Itoa(int(n))
}

View File

@ -0,0 +1,34 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !race
package impl
// There is no additional data as we're not running under race detector.
type RaceDetectHookData struct{}
// Empty stubs for when not using the race detector. Calls to these from index.go should be optimized away.
func (presence) raceDetectHookPresent(num uint32) {}
func (presence) raceDetectHookSetPresent(num uint32, size presenceSize) {}
func (presence) raceDetectHookClearPresent(num uint32) {}
func (presence) raceDetectHookAllocAndCopy(src presence) {}
// raceDetectHookPresent is called by the generated file interface
// (*proto.internalFuncs) Present to optionally read an unprotected
// shadow bitmap when race detection is enabled. In regular code it is
// a noop.
func raceDetectHookPresent(field *uint32, num uint32) {}
// raceDetectHookSetPresent is called by the generated file interface
// (*proto.internalFuncs) SetPresent to optionally write an unprotected
// shadow bitmap when race detection is enabled. In regular code it is
// a noop.
func raceDetectHookSetPresent(field *uint32, num uint32, size presenceSize) {}
// raceDetectHookClearPresent is called by the generated file interface
// (*proto.internalFuncs) ClearPresent to optionally write an unprotected
// shadow bitmap when race detection is enabled. In regular code it is
// a noop.
func raceDetectHookClearPresent(field *uint32, num uint32) {}

View File

@ -0,0 +1,126 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build race
package impl
// When running under race detector, we add a presence map of bytes, that we can access
// in the hook functions so that we trigger the race detection whenever we have concurrent
// Read-Writes or Write-Writes. The race detector does not otherwise detect invalid concurrent
// access to lazy fields as all updates of bitmaps and pointers are done using atomic operations.
type RaceDetectHookData struct {
shadowPresence *[]byte
}
// Hooks for presence bitmap operations that allocate, read and write the shadowPresence
// using non-atomic operations.
func (data *RaceDetectHookData) raceDetectHookAlloc(size presenceSize) {
sp := make([]byte, size)
atomicStoreShadowPresence(&data.shadowPresence, &sp)
}
func (p presence) raceDetectHookPresent(num uint32) {
data := p.toRaceDetectData()
if data == nil {
return
}
sp := atomicLoadShadowPresence(&data.shadowPresence)
if sp != nil {
_ = (*sp)[num]
}
}
func (p presence) raceDetectHookSetPresent(num uint32, size presenceSize) {
data := p.toRaceDetectData()
if data == nil {
return
}
sp := atomicLoadShadowPresence(&data.shadowPresence)
if sp == nil {
data.raceDetectHookAlloc(size)
sp = atomicLoadShadowPresence(&data.shadowPresence)
}
(*sp)[num] = 1
}
func (p presence) raceDetectHookClearPresent(num uint32) {
data := p.toRaceDetectData()
if data == nil {
return
}
sp := atomicLoadShadowPresence(&data.shadowPresence)
if sp != nil {
(*sp)[num] = 0
}
}
// raceDetectHookAllocAndCopy allocates a new shadowPresence slice at lazy and copies
// shadowPresence bytes from src to lazy.
func (p presence) raceDetectHookAllocAndCopy(q presence) {
sData := q.toRaceDetectData()
dData := p.toRaceDetectData()
if sData == nil {
return
}
srcSp := atomicLoadShadowPresence(&sData.shadowPresence)
if srcSp == nil {
atomicStoreShadowPresence(&dData.shadowPresence, nil)
return
}
n := len(*srcSp)
dSlice := make([]byte, n)
atomicStoreShadowPresence(&dData.shadowPresence, &dSlice)
for i := 0; i < n; i++ {
dSlice[i] = (*srcSp)[i]
}
}
// raceDetectHookPresent is called by the generated file interface
// (*proto.internalFuncs) Present to optionally read an unprotected
// shadow bitmap when race detection is enabled. In regular code it is
// a noop.
func raceDetectHookPresent(field *uint32, num uint32) {
data := findPointerToRaceDetectData(field, num)
if data == nil {
return
}
sp := atomicLoadShadowPresence(&data.shadowPresence)
if sp != nil {
_ = (*sp)[num]
}
}
// raceDetectHookSetPresent is called by the generated file interface
// (*proto.internalFuncs) SetPresent to optionally write an unprotected
// shadow bitmap when race detection is enabled. In regular code it is
// a noop.
func raceDetectHookSetPresent(field *uint32, num uint32, size presenceSize) {
data := findPointerToRaceDetectData(field, num)
if data == nil {
return
}
sp := atomicLoadShadowPresence(&data.shadowPresence)
if sp == nil {
data.raceDetectHookAlloc(size)
sp = atomicLoadShadowPresence(&data.shadowPresence)
}
(*sp)[num] = 1
}
// raceDetectHookClearPresent is called by the generated file interface
// (*proto.internalFuncs) ClearPresent to optionally write an unprotected
// shadow bitmap when race detection is enabled. In regular code it is
// a noop.
func raceDetectHookClearPresent(field *uint32, num uint32) {
data := findPointerToRaceDetectData(field, num)
if data == nil {
return
}
sp := atomicLoadShadowPresence(&data.shadowPresence)
if sp != nil {
(*sp)[num] = 0
}
}

View File

@ -35,6 +35,12 @@ func (mi *MessageInfo) checkInitializedPointer(p pointer) error {
}
return nil
}
var presence presence
if mi.presenceOffset.IsValid() {
presence = p.Apply(mi.presenceOffset).PresenceInfo()
}
if mi.extensionOffset.IsValid() {
e := p.Apply(mi.extensionOffset).Extensions()
if err := mi.isInitExtensions(e); err != nil {
@ -45,6 +51,33 @@ func (mi *MessageInfo) checkInitializedPointer(p pointer) error {
if !f.isRequired && f.funcs.isInit == nil {
continue
}
if f.presenceIndex != noPresence {
if !presence.Present(f.presenceIndex) {
if f.isRequired {
return errors.RequiredNotSet(string(mi.Desc.Fields().ByNumber(f.num).FullName()))
}
continue
}
if f.funcs.isInit != nil {
f.mi.init()
if f.mi.needsInitCheck {
if f.isLazy && p.Apply(f.offset).AtomicGetPointer().IsNil() {
lazy := *p.Apply(mi.lazyOffset).LazyInfoPtr()
if !lazy.AllowedPartial() {
// Nothing to see here, it was checked on unmarshal
continue
}
mi.lazyUnmarshal(p, f.num)
}
if err := f.funcs.isInit(p.Apply(f.offset), f); err != nil {
return err
}
}
}
continue
}
fptr := p.Apply(f.offset)
if f.isPointer && fptr.Elem().IsNil() {
if f.isRequired {

View File

@ -0,0 +1,264 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"fmt"
"reflect"
"google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/internal/errors"
"google.golang.org/protobuf/reflect/protoreflect"
)
func makeOpaqueMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) (*MessageInfo, pointerCoderFuncs) {
mi := getMessageInfo(ft)
if mi == nil {
panic(fmt.Sprintf("invalid field: %v: unsupported message type %v", fd.FullName(), ft))
}
switch fd.Kind() {
case protoreflect.MessageKind:
return mi, pointerCoderFuncs{
size: sizeOpaqueMessage,
marshal: appendOpaqueMessage,
unmarshal: consumeOpaqueMessage,
isInit: isInitOpaqueMessage,
merge: mergeOpaqueMessage,
}
case protoreflect.GroupKind:
return mi, pointerCoderFuncs{
size: sizeOpaqueGroup,
marshal: appendOpaqueGroup,
unmarshal: consumeOpaqueGroup,
isInit: isInitOpaqueMessage,
merge: mergeOpaqueMessage,
}
}
panic("unexpected field kind")
}
func sizeOpaqueMessage(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
return protowire.SizeBytes(f.mi.sizePointer(p.AtomicGetPointer(), opts)) + f.tagsize
}
func appendOpaqueMessage(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
mp := p.AtomicGetPointer()
calculatedSize := f.mi.sizePointer(mp, opts)
b = protowire.AppendVarint(b, f.wiretag)
b = protowire.AppendVarint(b, uint64(calculatedSize))
before := len(b)
b, err := f.mi.marshalAppendPointer(b, mp, opts)
if measuredSize := len(b) - before; calculatedSize != measuredSize && err == nil {
return nil, errors.MismatchedSizeCalculation(calculatedSize, measuredSize)
}
return b, err
}
func consumeOpaqueMessage(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
if wtyp != protowire.BytesType {
return out, errUnknown
}
v, n := protowire.ConsumeBytes(b)
if n < 0 {
return out, errDecode
}
mp := p.AtomicGetPointer()
if mp.IsNil() {
mp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())))
}
o, err := f.mi.unmarshalPointer(v, mp, 0, opts)
if err != nil {
return out, err
}
out.n = n
out.initialized = o.initialized
return out, nil
}
func isInitOpaqueMessage(p pointer, f *coderFieldInfo) error {
mp := p.AtomicGetPointer()
if mp.IsNil() {
return nil
}
return f.mi.checkInitializedPointer(mp)
}
func mergeOpaqueMessage(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
dstmp := dst.AtomicGetPointer()
if dstmp.IsNil() {
dstmp = dst.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())))
}
f.mi.mergePointer(dstmp, src.AtomicGetPointer(), opts)
}
func sizeOpaqueGroup(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
return 2*f.tagsize + f.mi.sizePointer(p.AtomicGetPointer(), opts)
}
func appendOpaqueGroup(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
b = protowire.AppendVarint(b, f.wiretag) // start group
b, err := f.mi.marshalAppendPointer(b, p.AtomicGetPointer(), opts)
b = protowire.AppendVarint(b, f.wiretag+1) // end group
return b, err
}
func consumeOpaqueGroup(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
if wtyp != protowire.StartGroupType {
return out, errUnknown
}
mp := p.AtomicGetPointer()
if mp.IsNil() {
mp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.mi.GoReflectType.Elem())))
}
o, e := f.mi.unmarshalPointer(b, mp, f.num, opts)
return o, e
}
func makeOpaqueRepeatedMessageFieldCoder(fd protoreflect.FieldDescriptor, ft reflect.Type) (*MessageInfo, pointerCoderFuncs) {
if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice {
panic(fmt.Sprintf("invalid field: %v: unsupported type for opaque repeated message: %v", fd.FullName(), ft))
}
mt := ft.Elem().Elem() // *[]*T -> *T
mi := getMessageInfo(mt)
if mi == nil {
panic(fmt.Sprintf("invalid field: %v: unsupported message type %v", fd.FullName(), mt))
}
switch fd.Kind() {
case protoreflect.MessageKind:
return mi, pointerCoderFuncs{
size: sizeOpaqueMessageSlice,
marshal: appendOpaqueMessageSlice,
unmarshal: consumeOpaqueMessageSlice,
isInit: isInitOpaqueMessageSlice,
merge: mergeOpaqueMessageSlice,
}
case protoreflect.GroupKind:
return mi, pointerCoderFuncs{
size: sizeOpaqueGroupSlice,
marshal: appendOpaqueGroupSlice,
unmarshal: consumeOpaqueGroupSlice,
isInit: isInitOpaqueMessageSlice,
merge: mergeOpaqueMessageSlice,
}
}
panic("unexpected field kind")
}
func sizeOpaqueMessageSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
s := p.AtomicGetPointer().PointerSlice()
n := 0
for _, v := range s {
n += protowire.SizeBytes(f.mi.sizePointer(v, opts)) + f.tagsize
}
return n
}
func appendOpaqueMessageSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
s := p.AtomicGetPointer().PointerSlice()
var err error
for _, v := range s {
b = protowire.AppendVarint(b, f.wiretag)
siz := f.mi.sizePointer(v, opts)
b = protowire.AppendVarint(b, uint64(siz))
before := len(b)
b, err = f.mi.marshalAppendPointer(b, v, opts)
if err != nil {
return b, err
}
if measuredSize := len(b) - before; siz != measuredSize {
return nil, errors.MismatchedSizeCalculation(siz, measuredSize)
}
}
return b, nil
}
func consumeOpaqueMessageSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
if wtyp != protowire.BytesType {
return out, errUnknown
}
v, n := protowire.ConsumeBytes(b)
if n < 0 {
return out, errDecode
}
mp := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))
o, err := f.mi.unmarshalPointer(v, mp, 0, opts)
if err != nil {
return out, err
}
sp := p.AtomicGetPointer()
if sp.IsNil() {
sp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem())))
}
sp.AppendPointerSlice(mp)
out.n = n
out.initialized = o.initialized
return out, nil
}
func isInitOpaqueMessageSlice(p pointer, f *coderFieldInfo) error {
sp := p.AtomicGetPointer()
if sp.IsNil() {
return nil
}
s := sp.PointerSlice()
for _, v := range s {
if err := f.mi.checkInitializedPointer(v); err != nil {
return err
}
}
return nil
}
func mergeOpaqueMessageSlice(dst, src pointer, f *coderFieldInfo, opts mergeOptions) {
ds := dst.AtomicGetPointer()
if ds.IsNil() {
ds = dst.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem())))
}
for _, sp := range src.AtomicGetPointer().PointerSlice() {
dm := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))
f.mi.mergePointer(dm, sp, opts)
ds.AppendPointerSlice(dm)
}
}
func sizeOpaqueGroupSlice(p pointer, f *coderFieldInfo, opts marshalOptions) (size int) {
s := p.AtomicGetPointer().PointerSlice()
n := 0
for _, v := range s {
n += 2*f.tagsize + f.mi.sizePointer(v, opts)
}
return n
}
func appendOpaqueGroupSlice(b []byte, p pointer, f *coderFieldInfo, opts marshalOptions) ([]byte, error) {
s := p.AtomicGetPointer().PointerSlice()
var err error
for _, v := range s {
b = protowire.AppendVarint(b, f.wiretag) // start group
b, err = f.mi.marshalAppendPointer(b, v, opts)
if err != nil {
return b, err
}
b = protowire.AppendVarint(b, f.wiretag+1) // end group
}
return b, nil
}
func consumeOpaqueGroupSlice(b []byte, p pointer, wtyp protowire.Type, f *coderFieldInfo, opts unmarshalOptions) (out unmarshalOutput, err error) {
if wtyp != protowire.StartGroupType {
return out, errUnknown
}
mp := pointerOfValue(reflect.New(f.mi.GoReflectType.Elem()))
out, err = f.mi.unmarshalPointer(b, mp, f.num, opts)
if err != nil {
return out, err
}
sp := p.AtomicGetPointer()
if sp.IsNil() {
sp = p.AtomicSetPointerIfNil(pointerOfValue(reflect.New(f.ft.Elem())))
}
sp.AppendPointerSlice(mp)
return out, err
}

View File

@ -32,6 +32,10 @@ type coderMessageInfo struct {
needsInitCheck bool
isMessageSet bool
numRequiredFields uint8
lazyOffset offset
presenceOffset offset
presenceSize presenceSize
}
type coderFieldInfo struct {
@ -45,12 +49,19 @@ type coderFieldInfo struct {
tagsize int // size of the varint-encoded tag
isPointer bool // true if IsNil may be called on the struct field
isRequired bool // true if field is required
isLazy bool
presenceIndex uint32
}
const noPresence = 0xffffffff
func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) {
mi.sizecacheOffset = invalidOffset
mi.unknownOffset = invalidOffset
mi.extensionOffset = invalidOffset
mi.lazyOffset = invalidOffset
mi.presenceOffset = si.presenceOffset
if si.sizecacheOffset.IsValid() && si.sizecacheType == sizecacheType {
mi.sizecacheOffset = si.sizecacheOffset
@ -127,6 +138,8 @@ func (mi *MessageInfo) makeCoderMethods(t reflect.Type, si structInfo) {
validation: newFieldValidationInfo(mi, si, fd, ft),
isPointer: fd.Cardinality() == protoreflect.Repeated || fd.HasPresence(),
isRequired: fd.Cardinality() == protoreflect.Required,
presenceIndex: noPresence,
}
mi.orderedCoderFields = append(mi.orderedCoderFields, cf)
mi.coderFields[cf.num] = cf

View File

@ -0,0 +1,156 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"fmt"
"reflect"
"sort"
"google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/internal/encoding/messageset"
"google.golang.org/protobuf/internal/order"
"google.golang.org/protobuf/reflect/protoreflect"
piface "google.golang.org/protobuf/runtime/protoiface"
)
func (mi *MessageInfo) makeOpaqueCoderMethods(t reflect.Type, si opaqueStructInfo) {
mi.sizecacheOffset = si.sizecacheOffset
mi.unknownOffset = si.unknownOffset
mi.unknownPtrKind = si.unknownType.Kind() == reflect.Ptr
mi.extensionOffset = si.extensionOffset
mi.lazyOffset = si.lazyOffset
mi.presenceOffset = si.presenceOffset
mi.coderFields = make(map[protowire.Number]*coderFieldInfo)
fields := mi.Desc.Fields()
for i := 0; i < fields.Len(); i++ {
fd := fields.Get(i)
fs := si.fieldsByNumber[fd.Number()]
if fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic() {
fs = si.oneofsByName[fd.ContainingOneof().Name()]
}
ft := fs.Type
var wiretag uint64
if !fd.IsPacked() {
wiretag = protowire.EncodeTag(fd.Number(), wireTypes[fd.Kind()])
} else {
wiretag = protowire.EncodeTag(fd.Number(), protowire.BytesType)
}
var fieldOffset offset
var funcs pointerCoderFuncs
var childMessage *MessageInfo
switch {
case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
fieldOffset = offsetOf(fs, mi.Exporter)
case fd.IsWeak():
fieldOffset = si.weakOffset
funcs = makeWeakMessageFieldCoder(fd)
case fd.Message() != nil && !fd.IsMap():
fieldOffset = offsetOf(fs, mi.Exporter)
if fd.IsList() {
childMessage, funcs = makeOpaqueRepeatedMessageFieldCoder(fd, ft)
} else {
childMessage, funcs = makeOpaqueMessageFieldCoder(fd, ft)
}
default:
fieldOffset = offsetOf(fs, mi.Exporter)
childMessage, funcs = fieldCoder(fd, ft)
}
cf := &coderFieldInfo{
num: fd.Number(),
offset: fieldOffset,
wiretag: wiretag,
ft: ft,
tagsize: protowire.SizeVarint(wiretag),
funcs: funcs,
mi: childMessage,
validation: newFieldValidationInfo(mi, si.structInfo, fd, ft),
isPointer: (fd.Cardinality() == protoreflect.Repeated ||
fd.Kind() == protoreflect.MessageKind ||
fd.Kind() == protoreflect.GroupKind),
isRequired: fd.Cardinality() == protoreflect.Required,
presenceIndex: noPresence,
}
// TODO: Use presence for all fields.
//
// In some cases, such as maps, presence means only "might be set" rather
// than "is definitely set", but every field should have a presence bit to
// permit us to skip over definitely-unset fields at marshal time.
var hasPresence bool
hasPresence, cf.isLazy = usePresenceForField(si, fd)
if hasPresence {
cf.presenceIndex, mi.presenceSize = presenceIndex(mi.Desc, fd)
}
mi.orderedCoderFields = append(mi.orderedCoderFields, cf)
mi.coderFields[cf.num] = cf
}
for i, oneofs := 0, mi.Desc.Oneofs(); i < oneofs.Len(); i++ {
if od := oneofs.Get(i); !od.IsSynthetic() {
mi.initOneofFieldCoders(od, si.structInfo)
}
}
if messageset.IsMessageSet(mi.Desc) {
if !mi.extensionOffset.IsValid() {
panic(fmt.Sprintf("%v: MessageSet with no extensions field", mi.Desc.FullName()))
}
if !mi.unknownOffset.IsValid() {
panic(fmt.Sprintf("%v: MessageSet with no unknown field", mi.Desc.FullName()))
}
mi.isMessageSet = true
}
sort.Slice(mi.orderedCoderFields, func(i, j int) bool {
return mi.orderedCoderFields[i].num < mi.orderedCoderFields[j].num
})
var maxDense protoreflect.FieldNumber
for _, cf := range mi.orderedCoderFields {
if cf.num >= 16 && cf.num >= 2*maxDense {
break
}
maxDense = cf.num
}
mi.denseCoderFields = make([]*coderFieldInfo, maxDense+1)
for _, cf := range mi.orderedCoderFields {
if int(cf.num) > len(mi.denseCoderFields) {
break
}
mi.denseCoderFields[cf.num] = cf
}
// To preserve compatibility with historic wire output, marshal oneofs last.
if mi.Desc.Oneofs().Len() > 0 {
sort.Slice(mi.orderedCoderFields, func(i, j int) bool {
fi := fields.ByNumber(mi.orderedCoderFields[i].num)
fj := fields.ByNumber(mi.orderedCoderFields[j].num)
return order.LegacyFieldOrder(fi, fj)
})
}
mi.needsInitCheck = needsInitCheck(mi.Desc)
if mi.methods.Marshal == nil && mi.methods.Size == nil {
mi.methods.Flags |= piface.SupportMarshalDeterministic
mi.methods.Marshal = mi.marshal
mi.methods.Size = mi.size
}
if mi.methods.Unmarshal == nil {
mi.methods.Flags |= piface.SupportUnmarshalDiscardUnknown
mi.methods.Unmarshal = mi.unmarshal
}
if mi.methods.CheckInitialized == nil {
mi.methods.CheckInitialized = mi.checkInitialized
}
if mi.methods.Merge == nil {
mi.methods.Merge = mi.merge
}
if mi.methods.Equal == nil {
mi.methods.Equal = equal
}
}

View File

@ -34,6 +34,8 @@ func (o unmarshalOptions) Options() proto.UnmarshalOptions {
AllowPartial: true,
DiscardUnknown: o.DiscardUnknown(),
Resolver: o.resolver,
NoLazyDecoding: o.NoLazyDecoding(),
}
}
@ -41,13 +43,26 @@ func (o unmarshalOptions) DiscardUnknown() bool {
return o.flags&protoiface.UnmarshalDiscardUnknown != 0
}
func (o unmarshalOptions) IsDefault() bool {
return o.flags == 0 && o.resolver == protoregistry.GlobalTypes
func (o unmarshalOptions) AliasBuffer() bool { return o.flags&protoiface.UnmarshalAliasBuffer != 0 }
func (o unmarshalOptions) Validated() bool { return o.flags&protoiface.UnmarshalValidated != 0 }
func (o unmarshalOptions) NoLazyDecoding() bool {
return o.flags&protoiface.UnmarshalNoLazyDecoding != 0
}
func (o unmarshalOptions) CanBeLazy() bool {
if o.resolver != protoregistry.GlobalTypes {
return false
}
// We ignore the UnmarshalInvalidateSizeCache even though it's not in the default set
return (o.flags & ^(protoiface.UnmarshalAliasBuffer | protoiface.UnmarshalValidated | protoiface.UnmarshalCheckRequired)) == 0
}
var lazyUnmarshalOptions = unmarshalOptions{
resolver: protoregistry.GlobalTypes,
depth: protowire.DefaultRecursionLimit,
flags: protoiface.UnmarshalAliasBuffer | protoiface.UnmarshalValidated,
depth: protowire.DefaultRecursionLimit,
}
type unmarshalOutput struct {
@ -94,9 +109,30 @@ func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire.
if flags.ProtoLegacy && mi.isMessageSet {
return unmarshalMessageSet(mi, b, p, opts)
}
lazyDecoding := LazyEnabled() // default
if opts.NoLazyDecoding() {
lazyDecoding = false // explicitly disabled
}
if mi.lazyOffset.IsValid() && lazyDecoding {
return mi.unmarshalPointerLazy(b, p, groupTag, opts)
}
return mi.unmarshalPointerEager(b, p, groupTag, opts)
}
// unmarshalPointerEager is the message unmarshalling function for all messages that are not lazy.
// The corresponding function for Lazy is in google_lazy.go.
func (mi *MessageInfo) unmarshalPointerEager(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) {
initialized := true
var requiredMask uint64
var exts *map[int32]ExtensionField
var presence presence
if mi.presenceOffset.IsValid() {
presence = p.Apply(mi.presenceOffset).PresenceInfo()
}
start := len(b)
for len(b) > 0 {
// Parse the tag (field number and wire type).
@ -154,6 +190,11 @@ func (mi *MessageInfo) unmarshalPointer(b []byte, p pointer, groupTag protowire.
if f.funcs.isInit != nil && !o.initialized {
initialized = false
}
if f.presenceIndex != noPresence {
presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
}
default:
// Possible extension.
if exts == nil && mi.extensionOffset.IsValid() {
@ -222,7 +263,7 @@ func (mi *MessageInfo) unmarshalExtension(b []byte, num protowire.Number, wtyp p
return out, errUnknown
}
if flags.LazyUnmarshalExtensions {
if opts.IsDefault() && x.canLazy(xt) {
if opts.CanBeLazy() && x.canLazy(xt) {
out, valid := skipExtension(b, xi, num, wtyp, opts)
switch valid {
case ValidationValid:
@ -270,6 +311,13 @@ func skipExtension(b []byte, xi *extensionFieldInfo, num protowire.Number, wtyp
if n < 0 {
return out, ValidationUnknown
}
if opts.Validated() {
out.initialized = true
out.n = n
return out, ValidationValid
}
out, st := xi.validation.mi.validate(v, 0, opts)
out.n = n
return out, st

View File

@ -10,6 +10,7 @@ import (
"sync/atomic"
"google.golang.org/protobuf/internal/flags"
"google.golang.org/protobuf/internal/protolazy"
"google.golang.org/protobuf/proto"
piface "google.golang.org/protobuf/runtime/protoiface"
)
@ -71,11 +72,39 @@ func (mi *MessageInfo) sizePointerSlow(p pointer, opts marshalOptions) (size int
e := p.Apply(mi.extensionOffset).Extensions()
size += mi.sizeExtensions(e, opts)
}
var lazy **protolazy.XXX_lazyUnmarshalInfo
var presence presence
if mi.presenceOffset.IsValid() {
presence = p.Apply(mi.presenceOffset).PresenceInfo()
if mi.lazyOffset.IsValid() {
lazy = p.Apply(mi.lazyOffset).LazyInfoPtr()
}
}
for _, f := range mi.orderedCoderFields {
if f.funcs.size == nil {
continue
}
fptr := p.Apply(f.offset)
if f.presenceIndex != noPresence {
if !presence.Present(f.presenceIndex) {
continue
}
if f.isLazy && fptr.AtomicGetPointer().IsNil() {
if lazyFields(opts) {
size += (*lazy).SizeField(uint32(f.num))
continue
} else {
mi.lazyUnmarshal(p, f.num)
}
}
size += f.funcs.size(fptr, f, opts)
continue
}
if f.isPointer && fptr.Elem().IsNil() {
continue
}
@ -134,11 +163,52 @@ func (mi *MessageInfo) marshalAppendPointer(b []byte, p pointer, opts marshalOpt
return b, err
}
}
var lazy **protolazy.XXX_lazyUnmarshalInfo
var presence presence
if mi.presenceOffset.IsValid() {
presence = p.Apply(mi.presenceOffset).PresenceInfo()
if mi.lazyOffset.IsValid() {
lazy = p.Apply(mi.lazyOffset).LazyInfoPtr()
}
}
for _, f := range mi.orderedCoderFields {
if f.funcs.marshal == nil {
continue
}
fptr := p.Apply(f.offset)
if f.presenceIndex != noPresence {
if !presence.Present(f.presenceIndex) {
continue
}
if f.isLazy {
// Be careful, this field needs to be read atomically, like for a get
if f.isPointer && fptr.AtomicGetPointer().IsNil() {
if lazyFields(opts) {
b, _ = (*lazy).AppendField(b, uint32(f.num))
continue
} else {
mi.lazyUnmarshal(p, f.num)
}
}
b, err = f.funcs.marshal(b, fptr, f, opts)
if err != nil {
return b, err
}
continue
} else if f.isPointer && fptr.Elem().IsNil() {
continue
}
b, err = f.funcs.marshal(b, fptr, f, opts)
if err != nil {
return b, err
}
continue
}
if f.isPointer && fptr.Elem().IsNil() {
continue
}
@ -163,6 +233,14 @@ func fullyLazyExtensions(opts marshalOptions) bool {
return opts.flags&piface.MarshalDeterministic == 0
}
// lazyFields returns true if we should attempt to keep fields lazy over size and marshal.
func lazyFields(opts marshalOptions) bool {
// When deterministic marshaling is requested, force an unmarshal for lazy
// fields to produce a deterministic result, instead of passing through
// bytes lazily that may or may not match what Go Protobuf would produce.
return opts.flags&piface.MarshalDeterministic == 0
}
func (mi *MessageInfo) sizeExtensions(ext *map[int32]ExtensionField, opts marshalOptions) (n int) {
if ext == nil {
return 0

433
vendor/google.golang.org/protobuf/internal/impl/lazy.go generated vendored Normal file
View File

@ -0,0 +1,433 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"fmt"
"math/bits"
"os"
"reflect"
"sort"
"sync/atomic"
"google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/internal/errors"
"google.golang.org/protobuf/internal/protolazy"
"google.golang.org/protobuf/reflect/protoreflect"
preg "google.golang.org/protobuf/reflect/protoregistry"
piface "google.golang.org/protobuf/runtime/protoiface"
)
var enableLazy int32 = func() int32 {
if os.Getenv("GOPROTODEBUG") == "nolazy" {
return 0
}
return 1
}()
// EnableLazyUnmarshal enables lazy unmarshaling.
func EnableLazyUnmarshal(enable bool) {
if enable {
atomic.StoreInt32(&enableLazy, 1)
return
}
atomic.StoreInt32(&enableLazy, 0)
}
// LazyEnabled reports whether lazy unmarshalling is currently enabled.
func LazyEnabled() bool {
return atomic.LoadInt32(&enableLazy) != 0
}
// UnmarshalField unmarshals a field in a message.
func UnmarshalField(m interface{}, num protowire.Number) {
switch m := m.(type) {
case *messageState:
m.messageInfo().lazyUnmarshal(m.pointer(), num)
case *messageReflectWrapper:
m.messageInfo().lazyUnmarshal(m.pointer(), num)
default:
panic(fmt.Sprintf("unsupported wrapper type %T", m))
}
}
func (mi *MessageInfo) lazyUnmarshal(p pointer, num protoreflect.FieldNumber) {
var f *coderFieldInfo
if int(num) < len(mi.denseCoderFields) {
f = mi.denseCoderFields[num]
} else {
f = mi.coderFields[num]
}
if f == nil {
panic(fmt.Sprintf("lazyUnmarshal: field info for %v.%v", mi.Desc.FullName(), num))
}
lazy := *p.Apply(mi.lazyOffset).LazyInfoPtr()
start, end, found, _, multipleEntries := lazy.FindFieldInProto(uint32(num))
if !found && multipleEntries == nil {
panic(fmt.Sprintf("lazyUnmarshal: can't find field data for %v.%v", mi.Desc.FullName(), num))
}
// The actual pointer in the message can not be set until the whole struct is filled in, otherwise we will have races.
// Create another pointer and set it atomically, if we won the race and the pointer in the original message is still nil.
fp := pointerOfValue(reflect.New(f.ft))
if multipleEntries != nil {
for _, entry := range multipleEntries {
mi.unmarshalField(lazy.Buffer()[entry.Start:entry.End], fp, f, lazy, lazy.UnmarshalFlags())
}
} else {
mi.unmarshalField(lazy.Buffer()[start:end], fp, f, lazy, lazy.UnmarshalFlags())
}
p.Apply(f.offset).AtomicSetPointerIfNil(fp.Elem())
}
func (mi *MessageInfo) unmarshalField(b []byte, p pointer, f *coderFieldInfo, lazyInfo *protolazy.XXX_lazyUnmarshalInfo, flags piface.UnmarshalInputFlags) error {
opts := lazyUnmarshalOptions
opts.flags |= flags
for len(b) > 0 {
// Parse the tag (field number and wire type).
var tag uint64
if b[0] < 0x80 {
tag = uint64(b[0])
b = b[1:]
} else if len(b) >= 2 && b[1] < 128 {
tag = uint64(b[0]&0x7f) + uint64(b[1])<<7
b = b[2:]
} else {
var n int
tag, n = protowire.ConsumeVarint(b)
if n < 0 {
return errors.New("invalid wire data")
}
b = b[n:]
}
var num protowire.Number
if n := tag >> 3; n < uint64(protowire.MinValidNumber) || n > uint64(protowire.MaxValidNumber) {
return errors.New("invalid wire data")
} else {
num = protowire.Number(n)
}
wtyp := protowire.Type(tag & 7)
if num == f.num {
o, err := f.funcs.unmarshal(b, p, wtyp, f, opts)
if err == nil {
b = b[o.n:]
continue
}
if err != errUnknown {
return err
}
}
n := protowire.ConsumeFieldValue(num, wtyp, b)
if n < 0 {
return errors.New("invalid wire data")
}
b = b[n:]
}
return nil
}
func (mi *MessageInfo) skipField(b []byte, f *coderFieldInfo, wtyp protowire.Type, opts unmarshalOptions) (out unmarshalOutput, _ ValidationStatus) {
fmi := f.validation.mi
if fmi == nil {
fd := mi.Desc.Fields().ByNumber(f.num)
if fd == nil || !fd.IsWeak() {
return out, ValidationUnknown
}
messageName := fd.Message().FullName()
messageType, err := preg.GlobalTypes.FindMessageByName(messageName)
if err != nil {
return out, ValidationUnknown
}
var ok bool
fmi, ok = messageType.(*MessageInfo)
if !ok {
return out, ValidationUnknown
}
}
fmi.init()
switch f.validation.typ {
case validationTypeMessage:
if wtyp != protowire.BytesType {
return out, ValidationWrongWireType
}
v, n := protowire.ConsumeBytes(b)
if n < 0 {
return out, ValidationInvalid
}
out, st := fmi.validate(v, 0, opts)
out.n = n
return out, st
case validationTypeGroup:
if wtyp != protowire.StartGroupType {
return out, ValidationWrongWireType
}
out, st := fmi.validate(b, f.num, opts)
return out, st
default:
return out, ValidationUnknown
}
}
// unmarshalPointerLazy is similar to unmarshalPointerEager, but it
// specifically handles lazy unmarshalling. it expects lazyOffset and
// presenceOffset to both be valid.
func (mi *MessageInfo) unmarshalPointerLazy(b []byte, p pointer, groupTag protowire.Number, opts unmarshalOptions) (out unmarshalOutput, err error) {
initialized := true
var requiredMask uint64
var lazy **protolazy.XXX_lazyUnmarshalInfo
var presence presence
var lazyIndex []protolazy.IndexEntry
var lastNum protowire.Number
outOfOrder := false
lazyDecode := false
presence = p.Apply(mi.presenceOffset).PresenceInfo()
lazy = p.Apply(mi.lazyOffset).LazyInfoPtr()
if !presence.AnyPresent(mi.presenceSize) {
if opts.CanBeLazy() {
// If the message contains existing data, we need to merge into it.
// Lazy unmarshaling doesn't merge, so only enable it when the
// message is empty (has no presence bitmap).
lazyDecode = true
if *lazy == nil {
*lazy = &protolazy.XXX_lazyUnmarshalInfo{}
}
(*lazy).SetUnmarshalFlags(opts.flags)
if !opts.AliasBuffer() {
// Make a copy of the buffer for lazy unmarshaling.
// Set the AliasBuffer flag so recursive unmarshal
// operations reuse the copy.
b = append([]byte{}, b...)
opts.flags |= piface.UnmarshalAliasBuffer
}
(*lazy).SetBuffer(b)
}
}
// Track special handling of lazy fields.
//
// In the common case, all fields are lazyValidateOnly (and lazyFields remains nil).
// In the event that validation for a field fails, this map tracks handling of the field.
type lazyAction uint8
const (
lazyValidateOnly lazyAction = iota // validate the field only
lazyUnmarshalNow // eagerly unmarshal the field
lazyUnmarshalLater // unmarshal the field after the message is fully processed
)
var lazyFields map[*coderFieldInfo]lazyAction
var exts *map[int32]ExtensionField
start := len(b)
pos := 0
for len(b) > 0 {
// Parse the tag (field number and wire type).
var tag uint64
if b[0] < 0x80 {
tag = uint64(b[0])
b = b[1:]
} else if len(b) >= 2 && b[1] < 128 {
tag = uint64(b[0]&0x7f) + uint64(b[1])<<7
b = b[2:]
} else {
var n int
tag, n = protowire.ConsumeVarint(b)
if n < 0 {
return out, errDecode
}
b = b[n:]
}
var num protowire.Number
if n := tag >> 3; n < uint64(protowire.MinValidNumber) || n > uint64(protowire.MaxValidNumber) {
return out, errors.New("invalid field number")
} else {
num = protowire.Number(n)
}
wtyp := protowire.Type(tag & 7)
if wtyp == protowire.EndGroupType {
if num != groupTag {
return out, errors.New("mismatching end group marker")
}
groupTag = 0
break
}
var f *coderFieldInfo
if int(num) < len(mi.denseCoderFields) {
f = mi.denseCoderFields[num]
} else {
f = mi.coderFields[num]
}
var n int
err := errUnknown
discardUnknown := false
Field:
switch {
case f != nil:
if f.funcs.unmarshal == nil {
break
}
if f.isLazy && lazyDecode {
switch {
case lazyFields == nil || lazyFields[f] == lazyValidateOnly:
// Attempt to validate this field and leave it for later lazy unmarshaling.
o, valid := mi.skipField(b, f, wtyp, opts)
switch valid {
case ValidationValid:
// Skip over the valid field and continue.
err = nil
presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
requiredMask |= f.validation.requiredBit
if !o.initialized {
initialized = false
}
n = o.n
break Field
case ValidationInvalid:
return out, errors.New("invalid proto wire format")
case ValidationWrongWireType:
break Field
case ValidationUnknown:
if lazyFields == nil {
lazyFields = make(map[*coderFieldInfo]lazyAction)
}
if presence.Present(f.presenceIndex) {
// We were unable to determine if the field is valid or not,
// and we've already skipped over at least one instance of this
// field. Clear the presence bit (so if we stop decoding early,
// we don't leave a partially-initialized field around) and flag
// the field for unmarshaling before we return.
presence.ClearPresent(f.presenceIndex)
lazyFields[f] = lazyUnmarshalLater
discardUnknown = true
break Field
} else {
// We were unable to determine if the field is valid or not,
// but this is the first time we've seen it. Flag it as needing
// eager unmarshaling and fall through to the eager unmarshal case below.
lazyFields[f] = lazyUnmarshalNow
}
}
case lazyFields[f] == lazyUnmarshalLater:
// This field will be unmarshaled in a separate pass below.
// Skip over it here.
discardUnknown = true
break Field
default:
// Eagerly unmarshal the field.
}
}
if f.isLazy && !lazyDecode && presence.Present(f.presenceIndex) {
if p.Apply(f.offset).AtomicGetPointer().IsNil() {
mi.lazyUnmarshal(p, f.num)
}
}
var o unmarshalOutput
o, err = f.funcs.unmarshal(b, p.Apply(f.offset), wtyp, f, opts)
n = o.n
if err != nil {
break
}
requiredMask |= f.validation.requiredBit
if f.funcs.isInit != nil && !o.initialized {
initialized = false
}
if f.presenceIndex != noPresence {
presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
}
default:
// Possible extension.
if exts == nil && mi.extensionOffset.IsValid() {
exts = p.Apply(mi.extensionOffset).Extensions()
if *exts == nil {
*exts = make(map[int32]ExtensionField)
}
}
if exts == nil {
break
}
var o unmarshalOutput
o, err = mi.unmarshalExtension(b, num, wtyp, *exts, opts)
if err != nil {
break
}
n = o.n
if !o.initialized {
initialized = false
}
}
if err != nil {
if err != errUnknown {
return out, err
}
n = protowire.ConsumeFieldValue(num, wtyp, b)
if n < 0 {
return out, errDecode
}
if !discardUnknown && !opts.DiscardUnknown() && mi.unknownOffset.IsValid() {
u := mi.mutableUnknownBytes(p)
*u = protowire.AppendTag(*u, num, wtyp)
*u = append(*u, b[:n]...)
}
}
b = b[n:]
end := start - len(b)
if lazyDecode && f != nil && f.isLazy {
if num != lastNum {
lazyIndex = append(lazyIndex, protolazy.IndexEntry{
FieldNum: uint32(num),
Start: uint32(pos),
End: uint32(end),
})
} else {
i := len(lazyIndex) - 1
lazyIndex[i].End = uint32(end)
lazyIndex[i].MultipleContiguous = true
}
}
if num < lastNum {
outOfOrder = true
}
pos = end
lastNum = num
}
if groupTag != 0 {
return out, errors.New("missing end group marker")
}
if lazyFields != nil {
// Some fields failed validation, and now need to be unmarshaled.
for f, action := range lazyFields {
if action != lazyUnmarshalLater {
continue
}
initialized = false
if *lazy == nil {
*lazy = &protolazy.XXX_lazyUnmarshalInfo{}
}
if err := mi.unmarshalField((*lazy).Buffer(), p.Apply(f.offset), f, *lazy, opts.flags); err != nil {
return out, err
}
presence.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
}
}
if lazyDecode {
if outOfOrder {
sort.Slice(lazyIndex, func(i, j int) bool {
return lazyIndex[i].FieldNum < lazyIndex[j].FieldNum ||
(lazyIndex[i].FieldNum == lazyIndex[j].FieldNum &&
lazyIndex[i].Start < lazyIndex[j].Start)
})
}
if *lazy == nil {
*lazy = &protolazy.XXX_lazyUnmarshalInfo{}
}
(*lazy).SetIndex(lazyIndex)
}
if mi.numRequiredFields > 0 && bits.OnesCount64(requiredMask) != int(mi.numRequiredFields) {
initialized = false
}
if initialized {
out.initialized = true
}
out.n = start - len(b)
return out, nil
}

View File

@ -41,11 +41,38 @@ func (mi *MessageInfo) mergePointer(dst, src pointer, opts mergeOptions) {
if src.IsNil() {
return
}
var presenceSrc presence
var presenceDst presence
if mi.presenceOffset.IsValid() {
presenceSrc = src.Apply(mi.presenceOffset).PresenceInfo()
presenceDst = dst.Apply(mi.presenceOffset).PresenceInfo()
}
for _, f := range mi.orderedCoderFields {
if f.funcs.merge == nil {
continue
}
sfptr := src.Apply(f.offset)
if f.presenceIndex != noPresence {
if !presenceSrc.Present(f.presenceIndex) {
continue
}
dfptr := dst.Apply(f.offset)
if f.isLazy {
if sfptr.AtomicGetPointer().IsNil() {
mi.lazyUnmarshal(src, f.num)
}
if presenceDst.Present(f.presenceIndex) && dfptr.AtomicGetPointer().IsNil() {
mi.lazyUnmarshal(dst, f.num)
}
}
f.funcs.merge(dst.Apply(f.offset), sfptr, f, opts)
presenceDst.SetPresentUnatomic(f.presenceIndex, mi.presenceSize)
continue
}
if f.isPointer && sfptr.Elem().IsNil() {
continue
}

View File

@ -79,6 +79,9 @@ func (mi *MessageInfo) initOnce() {
if mi.initDone == 1 {
return
}
if opaqueInitHook(mi) {
return
}
t := mi.GoReflectType
if t.Kind() != reflect.Ptr && t.Elem().Kind() != reflect.Struct {
@ -133,6 +136,9 @@ type structInfo struct {
extensionOffset offset
extensionType reflect.Type
lazyOffset offset
presenceOffset offset
fieldsByNumber map[protoreflect.FieldNumber]reflect.StructField
oneofsByName map[protoreflect.Name]reflect.StructField
oneofWrappersByType map[reflect.Type]protoreflect.FieldNumber
@ -145,6 +151,8 @@ func (mi *MessageInfo) makeStructInfo(t reflect.Type) structInfo {
weakOffset: invalidOffset,
unknownOffset: invalidOffset,
extensionOffset: invalidOffset,
lazyOffset: invalidOffset,
presenceOffset: invalidOffset,
fieldsByNumber: map[protoreflect.FieldNumber]reflect.StructField{},
oneofsByName: map[protoreflect.Name]reflect.StructField{},
@ -175,6 +183,10 @@ fieldLoop:
si.extensionOffset = offsetOf(f, mi.Exporter)
si.extensionType = f.Type
}
case "lazyFields", "XXX_lazyUnmarshalInfo":
si.lazyOffset = offsetOf(f, mi.Exporter)
case "XXX_presence":
si.presenceOffset = offsetOf(f, mi.Exporter)
default:
for _, s := range strings.Split(f.Tag.Get("protobuf"), ",") {
if len(s) > 0 && strings.Trim(s, "0123456789") == "" {

View File

@ -0,0 +1,614 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"fmt"
"math"
"reflect"
"strings"
"sync/atomic"
"google.golang.org/protobuf/reflect/protoreflect"
)
type opaqueStructInfo struct {
structInfo
}
// isOpaque determines whether a protobuf message type is on the Opaque API. It
// checks whether the type is a Go struct that protoc-gen-go would generate.
//
// This function only detects newly generated messages from the v2
// implementation of protoc-gen-go. It is unable to classify generated messages
// that are too old or those that are generated by a different generator
// such as protoc-gen-gogo.
func isOpaque(t reflect.Type) bool {
// The current detection mechanism is to simply check the first field
// for a struct tag with the "protogen" key.
if t.Kind() == reflect.Struct && t.NumField() > 0 {
pgt := t.Field(0).Tag.Get("protogen")
return strings.HasPrefix(pgt, "opaque.")
}
return false
}
func opaqueInitHook(mi *MessageInfo) bool {
mt := mi.GoReflectType.Elem()
si := opaqueStructInfo{
structInfo: mi.makeStructInfo(mt),
}
if !isOpaque(mt) {
return false
}
defer atomic.StoreUint32(&mi.initDone, 1)
mi.fields = map[protoreflect.FieldNumber]*fieldInfo{}
fds := mi.Desc.Fields()
for i := 0; i < fds.Len(); i++ {
fd := fds.Get(i)
fs := si.fieldsByNumber[fd.Number()]
var fi fieldInfo
usePresence, _ := usePresenceForField(si, fd)
switch {
case fd.IsWeak():
// Weak fields are no different for opaque.
fi = fieldInfoForWeakMessage(fd, si.weakOffset)
case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
// Oneofs are no different for opaque.
fi = fieldInfoForOneof(fd, si.oneofsByName[fd.ContainingOneof().Name()], mi.Exporter, si.oneofWrappersByNumber[fd.Number()])
case fd.IsMap():
fi = mi.fieldInfoForMapOpaque(si, fd, fs)
case fd.IsList() && fd.Message() == nil && usePresence:
fi = mi.fieldInfoForScalarListOpaque(si, fd, fs)
case fd.IsList() && fd.Message() == nil:
// Proto3 lists without presence can use same access methods as open
fi = fieldInfoForList(fd, fs, mi.Exporter)
case fd.IsList() && usePresence:
fi = mi.fieldInfoForMessageListOpaque(si, fd, fs)
case fd.IsList():
// Proto3 opaque messages that does not need presence bitmap.
// Different representation than open struct, but same logic
fi = mi.fieldInfoForMessageListOpaqueNoPresence(si, fd, fs)
case fd.Message() != nil && usePresence:
fi = mi.fieldInfoForMessageOpaque(si, fd, fs)
case fd.Message() != nil:
// Proto3 messages without presence can use same access methods as open
fi = fieldInfoForMessage(fd, fs, mi.Exporter)
default:
fi = mi.fieldInfoForScalarOpaque(si, fd, fs)
}
mi.fields[fd.Number()] = &fi
}
mi.oneofs = map[protoreflect.Name]*oneofInfo{}
for i := 0; i < mi.Desc.Oneofs().Len(); i++ {
od := mi.Desc.Oneofs().Get(i)
if !od.IsSynthetic() {
mi.oneofs[od.Name()] = makeOneofInfo(od, si.structInfo, mi.Exporter)
}
}
mi.denseFields = make([]*fieldInfo, fds.Len()*2)
for i := 0; i < fds.Len(); i++ {
if fd := fds.Get(i); int(fd.Number()) < len(mi.denseFields) {
mi.denseFields[fd.Number()] = mi.fields[fd.Number()]
}
}
for i := 0; i < fds.Len(); {
fd := fds.Get(i)
if od := fd.ContainingOneof(); od != nil && !fd.ContainingOneof().IsSynthetic() {
mi.rangeInfos = append(mi.rangeInfos, mi.oneofs[od.Name()])
i += od.Fields().Len()
} else {
mi.rangeInfos = append(mi.rangeInfos, mi.fields[fd.Number()])
i++
}
}
mi.makeExtensionFieldsFunc(mt, si.structInfo)
mi.makeUnknownFieldsFunc(mt, si.structInfo)
mi.makeOpaqueCoderMethods(mt, si)
mi.makeFieldTypes(si.structInfo)
return true
}
func (mi *MessageInfo) fieldInfoForMapOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
if ft.Kind() != reflect.Map {
panic(fmt.Sprintf("invalid type: got %v, want map kind", ft))
}
fieldOffset := offsetOf(fs, mi.Exporter)
conv := NewConverter(ft, fd)
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
// Don't bother checking presence bits, since we need to
// look at the map length even if the presence bit is set.
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
return rv.Len() > 0
},
clear: func(p pointer) {
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
rv.Set(reflect.Zero(rv.Type()))
},
get: func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if rv.Len() == 0 {
return conv.Zero()
}
return conv.PBValueOf(rv)
},
set: func(p pointer, v protoreflect.Value) {
pv := conv.GoValueOf(v)
if pv.IsNil() {
panic(fmt.Sprintf("invalid value: setting map field to read-only value"))
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
rv.Set(pv)
},
mutable: func(p pointer) protoreflect.Value {
v := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if v.IsNil() {
v.Set(reflect.MakeMap(fs.Type))
}
return conv.PBValueOf(v)
},
newField: func() protoreflect.Value {
return conv.New()
},
}
}
func (mi *MessageInfo) fieldInfoForScalarListOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
if ft.Kind() != reflect.Slice {
panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
}
conv := NewConverter(reflect.PtrTo(ft), fd)
fieldOffset := offsetOf(fs, mi.Exporter)
index, _ := presenceIndex(mi.Desc, fd)
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
return rv.Len() > 0
},
clear: func(p pointer) {
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
rv.Set(reflect.Zero(rv.Type()))
},
get: func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type)
if rv.Elem().Len() == 0 {
return conv.Zero()
}
return conv.PBValueOf(rv)
},
set: func(p pointer, v protoreflect.Value) {
pv := conv.GoValueOf(v)
if pv.IsNil() {
panic(fmt.Sprintf("invalid value: setting repeated field to read-only value"))
}
mi.setPresent(p, index)
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
rv.Set(pv.Elem())
},
mutable: func(p pointer) protoreflect.Value {
mi.setPresent(p, index)
return conv.PBValueOf(p.Apply(fieldOffset).AsValueOf(fs.Type))
},
newField: func() protoreflect.Value {
return conv.New()
},
}
}
func (mi *MessageInfo) fieldInfoForMessageListOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice {
panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
}
conv := NewConverter(ft, fd)
fieldOffset := offsetOf(fs, mi.Exporter)
index, _ := presenceIndex(mi.Desc, fd)
fieldNumber := fd.Number()
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
if !mi.present(p, index) {
return false
}
sp := p.Apply(fieldOffset).AtomicGetPointer()
if sp.IsNil() {
// Lazily unmarshal this field.
mi.lazyUnmarshal(p, fieldNumber)
sp = p.Apply(fieldOffset).AtomicGetPointer()
}
rv := sp.AsValueOf(fs.Type.Elem())
return rv.Elem().Len() > 0
},
clear: func(p pointer) {
fp := p.Apply(fieldOffset)
sp := fp.AtomicGetPointer()
if sp.IsNil() {
sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem())))
mi.setPresent(p, index)
}
rv := sp.AsValueOf(fs.Type.Elem())
rv.Elem().Set(reflect.Zero(rv.Type().Elem()))
},
get: func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
if !mi.present(p, index) {
return conv.Zero()
}
sp := p.Apply(fieldOffset).AtomicGetPointer()
if sp.IsNil() {
// Lazily unmarshal this field.
mi.lazyUnmarshal(p, fieldNumber)
sp = p.Apply(fieldOffset).AtomicGetPointer()
}
rv := sp.AsValueOf(fs.Type.Elem())
if rv.Elem().Len() == 0 {
return conv.Zero()
}
return conv.PBValueOf(rv)
},
set: func(p pointer, v protoreflect.Value) {
fp := p.Apply(fieldOffset)
sp := fp.AtomicGetPointer()
if sp.IsNil() {
sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem())))
mi.setPresent(p, index)
}
rv := sp.AsValueOf(fs.Type.Elem())
val := conv.GoValueOf(v)
if val.IsNil() {
panic(fmt.Sprintf("invalid value: setting repeated field to read-only value"))
} else {
rv.Elem().Set(val.Elem())
}
},
mutable: func(p pointer) protoreflect.Value {
fp := p.Apply(fieldOffset)
sp := fp.AtomicGetPointer()
if sp.IsNil() {
if mi.present(p, index) {
// Lazily unmarshal this field.
mi.lazyUnmarshal(p, fieldNumber)
sp = p.Apply(fieldOffset).AtomicGetPointer()
} else {
sp = fp.AtomicSetPointerIfNil(pointerOfValue(reflect.New(fs.Type.Elem())))
mi.setPresent(p, index)
}
}
rv := sp.AsValueOf(fs.Type.Elem())
return conv.PBValueOf(rv)
},
newField: func() protoreflect.Value {
return conv.New()
},
}
}
func (mi *MessageInfo) fieldInfoForMessageListOpaqueNoPresence(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
if ft.Kind() != reflect.Ptr || ft.Elem().Kind() != reflect.Slice {
panic(fmt.Sprintf("invalid type: got %v, want slice kind", ft))
}
conv := NewConverter(ft, fd)
fieldOffset := offsetOf(fs, mi.Exporter)
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
sp := p.Apply(fieldOffset).AtomicGetPointer()
if sp.IsNil() {
return false
}
rv := sp.AsValueOf(fs.Type.Elem())
return rv.Elem().Len() > 0
},
clear: func(p pointer) {
sp := p.Apply(fieldOffset).AtomicGetPointer()
if !sp.IsNil() {
rv := sp.AsValueOf(fs.Type.Elem())
rv.Elem().Set(reflect.Zero(rv.Type().Elem()))
}
},
get: func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
sp := p.Apply(fieldOffset).AtomicGetPointer()
if sp.IsNil() {
return conv.Zero()
}
rv := sp.AsValueOf(fs.Type.Elem())
if rv.Elem().Len() == 0 {
return conv.Zero()
}
return conv.PBValueOf(rv)
},
set: func(p pointer, v protoreflect.Value) {
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if rv.IsNil() {
rv.Set(reflect.New(fs.Type.Elem()))
}
val := conv.GoValueOf(v)
if val.IsNil() {
panic(fmt.Sprintf("invalid value: setting repeated field to read-only value"))
} else {
rv.Elem().Set(val.Elem())
}
},
mutable: func(p pointer) protoreflect.Value {
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if rv.IsNil() {
rv.Set(reflect.New(fs.Type.Elem()))
}
return conv.PBValueOf(rv)
},
newField: func() protoreflect.Value {
return conv.New()
},
}
}
func (mi *MessageInfo) fieldInfoForScalarOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
nullable := fd.HasPresence()
if oneof := fd.ContainingOneof(); oneof != nil && oneof.IsSynthetic() {
nullable = true
}
deref := false
if nullable && ft.Kind() == reflect.Ptr {
ft = ft.Elem()
deref = true
}
conv := NewConverter(ft, fd)
fieldOffset := offsetOf(fs, mi.Exporter)
index, _ := presenceIndex(mi.Desc, fd)
var getter func(p pointer) protoreflect.Value
if !nullable {
getter = getterForDirectScalar(fd, fs, conv, fieldOffset)
} else {
getter = getterForOpaqueNullableScalar(mi, index, fd, fs, conv, fieldOffset)
}
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
if nullable {
return mi.present(p, index)
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
switch rv.Kind() {
case reflect.Bool:
return rv.Bool()
case reflect.Int32, reflect.Int64:
return rv.Int() != 0
case reflect.Uint32, reflect.Uint64:
return rv.Uint() != 0
case reflect.Float32, reflect.Float64:
return rv.Float() != 0 || math.Signbit(rv.Float())
case reflect.String, reflect.Slice:
return rv.Len() > 0
default:
panic(fmt.Sprintf("invalid type: %v", rv.Type())) // should never happen
}
},
clear: func(p pointer) {
if nullable {
mi.clearPresent(p, index)
}
// This is only valuable for bytes and strings, but we do it unconditionally.
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
rv.Set(reflect.Zero(rv.Type()))
},
get: getter,
// TODO: Implement unsafe fast path for set?
set: func(p pointer, v protoreflect.Value) {
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if deref {
if rv.IsNil() {
rv.Set(reflect.New(ft))
}
rv = rv.Elem()
}
rv.Set(conv.GoValueOf(v))
if nullable && rv.Kind() == reflect.Slice && rv.IsNil() {
rv.Set(emptyBytes)
}
if nullable {
mi.setPresent(p, index)
}
},
newField: func() protoreflect.Value {
return conv.New()
},
}
}
func (mi *MessageInfo) fieldInfoForMessageOpaque(si opaqueStructInfo, fd protoreflect.FieldDescriptor, fs reflect.StructField) fieldInfo {
ft := fs.Type
conv := NewConverter(ft, fd)
fieldOffset := offsetOf(fs, mi.Exporter)
index, _ := presenceIndex(mi.Desc, fd)
fieldNumber := fd.Number()
elemType := fs.Type.Elem()
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
return mi.present(p, index)
},
clear: func(p pointer) {
mi.clearPresent(p, index)
p.Apply(fieldOffset).AtomicSetNilPointer()
},
get: func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
fp := p.Apply(fieldOffset)
mp := fp.AtomicGetPointer()
if mp.IsNil() {
// Lazily unmarshal this field.
mi.lazyUnmarshal(p, fieldNumber)
mp = fp.AtomicGetPointer()
}
rv := mp.AsValueOf(elemType)
return conv.PBValueOf(rv)
},
set: func(p pointer, v protoreflect.Value) {
val := pointerOfValue(conv.GoValueOf(v))
if val.IsNil() {
panic("invalid nil pointer")
}
p.Apply(fieldOffset).AtomicSetPointer(val)
mi.setPresent(p, index)
},
mutable: func(p pointer) protoreflect.Value {
fp := p.Apply(fieldOffset)
mp := fp.AtomicGetPointer()
if mp.IsNil() {
if mi.present(p, index) {
// Lazily unmarshal this field.
mi.lazyUnmarshal(p, fieldNumber)
mp = fp.AtomicGetPointer()
} else {
mp = pointerOfValue(conv.GoValueOf(conv.New()))
fp.AtomicSetPointer(mp)
mi.setPresent(p, index)
}
}
return conv.PBValueOf(mp.AsValueOf(fs.Type.Elem()))
},
newMessage: func() protoreflect.Message {
return conv.New().Message()
},
newField: func() protoreflect.Value {
return conv.New()
},
}
}
// A presenceList wraps a List, updating presence bits as necessary when the
// list contents change.
type presenceList struct {
pvalueList
setPresence func(bool)
}
type pvalueList interface {
protoreflect.List
//Unwrapper
}
func (list presenceList) Append(v protoreflect.Value) {
list.pvalueList.Append(v)
list.setPresence(true)
}
func (list presenceList) Truncate(i int) {
list.pvalueList.Truncate(i)
list.setPresence(i > 0)
}
// presenceIndex returns the index to pass to presence functions.
//
// TODO: field.Desc.Index() would be simpler, and would give space to record the presence of oneof fields.
func presenceIndex(md protoreflect.MessageDescriptor, fd protoreflect.FieldDescriptor) (uint32, presenceSize) {
found := false
var index, numIndices uint32
for i := 0; i < md.Fields().Len(); i++ {
f := md.Fields().Get(i)
if f == fd {
found = true
index = numIndices
}
if f.ContainingOneof() == nil || isLastOneofField(f) {
numIndices++
}
}
if !found {
panic(fmt.Sprintf("BUG: %v not in %v", fd.Name(), md.FullName()))
}
return index, presenceSize(numIndices)
}
func isLastOneofField(fd protoreflect.FieldDescriptor) bool {
fields := fd.ContainingOneof().Fields()
return fields.Get(fields.Len()-1) == fd
}
func (mi *MessageInfo) setPresent(p pointer, index uint32) {
p.Apply(mi.presenceOffset).PresenceInfo().SetPresent(index, mi.presenceSize)
}
func (mi *MessageInfo) clearPresent(p pointer, index uint32) {
p.Apply(mi.presenceOffset).PresenceInfo().ClearPresent(index)
}
func (mi *MessageInfo) present(p pointer, index uint32) bool {
return p.Apply(mi.presenceOffset).PresenceInfo().Present(index)
}
// usePresenceForField implements the somewhat intricate logic of when
// the presence bitmap is used for a field. The main logic is that a
// field that is optional or that can be lazy will use the presence
// bit, but for proto2, also maps have a presence bit. It also records
// if the field can ever be lazy, which is true if we have a
// lazyOffset and the field is a message or a slice of messages. A
// field that is lazy will always need a presence bit. Oneofs are not
// lazy and do not use presence, unless they are a synthetic oneof,
// which is a proto3 optional field. For proto3 optionals, we use the
// presence and they can also be lazy when applicable (a message).
func usePresenceForField(si opaqueStructInfo, fd protoreflect.FieldDescriptor) (usePresence, canBeLazy bool) {
hasLazyField := fd.(interface{ IsLazy() bool }).IsLazy()
// Non-oneof scalar fields with explicit field presence use the presence array.
usesPresenceArray := fd.HasPresence() && fd.Message() == nil && (fd.ContainingOneof() == nil || fd.ContainingOneof().IsSynthetic())
switch {
case fd.ContainingOneof() != nil && !fd.ContainingOneof().IsSynthetic():
return false, false
case fd.IsWeak():
return false, false
case fd.IsMap():
return false, false
case fd.Kind() == protoreflect.MessageKind || fd.Kind() == protoreflect.GroupKind:
return hasLazyField, hasLazyField
default:
return usesPresenceArray || (hasLazyField && fd.HasPresence()), false
}
}

View File

@ -0,0 +1,132 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by generate-types. DO NOT EDIT.
package impl
import (
"reflect"
"google.golang.org/protobuf/reflect/protoreflect"
)
func getterForOpaqueNullableScalar(mi *MessageInfo, index uint32, fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
ft := fs.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if fd.Kind() == protoreflect.EnumKind {
// Enums for nullable opaque types.
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
return conv.PBValueOf(rv)
}
}
switch ft.Kind() {
case reflect.Bool:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bool()
return protoreflect.ValueOfBool(*x)
}
case reflect.Int32:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Int32()
return protoreflect.ValueOfInt32(*x)
}
case reflect.Uint32:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Uint32()
return protoreflect.ValueOfUint32(*x)
}
case reflect.Int64:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Int64()
return protoreflect.ValueOfInt64(*x)
}
case reflect.Uint64:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Uint64()
return protoreflect.ValueOfUint64(*x)
}
case reflect.Float32:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Float32()
return protoreflect.ValueOfFloat32(*x)
}
case reflect.Float64:
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Float64()
return protoreflect.ValueOfFloat64(*x)
}
case reflect.String:
if fd.Kind() == protoreflect.BytesKind {
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).StringPtr()
if *x == nil {
return conv.Zero()
}
if len(**x) == 0 {
return protoreflect.ValueOfBytes(nil)
}
return protoreflect.ValueOfBytes([]byte(**x))
}
}
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).StringPtr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfString(**x)
}
case reflect.Slice:
if fd.Kind() == protoreflect.StringKind {
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bytes()
return protoreflect.ValueOfString(string(*x))
}
}
return func(p pointer) protoreflect.Value {
if p.IsNil() || !mi.present(p, index) {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bytes()
return protoreflect.ValueOfBytes(*x)
}
}
panic("unexpected protobuf kind: " + ft.Kind().String())
}

View File

@ -205,6 +205,11 @@ func (mi *MessageInfo) makeFieldTypes(si structInfo) {
case fd.IsList():
if fd.Enum() != nil || fd.Message() != nil {
ft = fs.Type.Elem()
if ft.Kind() == reflect.Slice {
ft = ft.Elem()
}
}
isMessage = fd.Message() != nil
case fd.Enum() != nil:

View File

@ -256,6 +256,7 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField,
ft := fs.Type
nullable := fd.HasPresence()
isBytes := ft.Kind() == reflect.Slice && ft.Elem().Kind() == reflect.Uint8
var getter func(p pointer) protoreflect.Value
if nullable {
if ft.Kind() != reflect.Ptr && ft.Kind() != reflect.Slice {
// This never occurs for generated message types.
@ -268,19 +269,25 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField,
}
}
conv := NewConverter(ft, fd)
// TODO: Implement unsafe fast path?
fieldOffset := offsetOf(fs, x)
// Generate specialized getter functions to avoid going through reflect.Value
if nullable {
getter = getterForNullableScalar(fd, fs, conv, fieldOffset)
} else {
getter = getterForDirectScalar(fd, fs, conv, fieldOffset)
}
return fieldInfo{
fieldDesc: fd,
has: func(p pointer) bool {
if p.IsNil() {
return false
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if nullable {
return !rv.IsNil()
return !p.Apply(fieldOffset).Elem().IsNil()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
switch rv.Kind() {
case reflect.Bool:
return rv.Bool()
@ -300,21 +307,8 @@ func fieldInfoForScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField,
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
rv.Set(reflect.Zero(rv.Type()))
},
get: func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if nullable {
if rv.IsNil() {
return conv.Zero()
}
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
}
return conv.PBValueOf(rv)
},
get: getter,
// TODO: Implement unsafe fast path for set?
set: func(p pointer, v protoreflect.Value) {
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
if nullable && rv.Kind() == reflect.Ptr {

View File

@ -0,0 +1,273 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by generate-types. DO NOT EDIT.
package impl
import (
"reflect"
"google.golang.org/protobuf/reflect/protoreflect"
)
func getterForNullableScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
ft := fs.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if fd.Kind() == protoreflect.EnumKind {
elemType := fs.Type.Elem()
// Enums for nullable types.
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
rv := p.Apply(fieldOffset).Elem().AsValueOf(elemType)
if rv.IsNil() {
return conv.Zero()
}
return conv.PBValueOf(rv.Elem())
}
}
switch ft.Kind() {
case reflect.Bool:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).BoolPtr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfBool(**x)
}
case reflect.Int32:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Int32Ptr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfInt32(**x)
}
case reflect.Uint32:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Uint32Ptr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfUint32(**x)
}
case reflect.Int64:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Int64Ptr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfInt64(**x)
}
case reflect.Uint64:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Uint64Ptr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfUint64(**x)
}
case reflect.Float32:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Float32Ptr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfFloat32(**x)
}
case reflect.Float64:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Float64Ptr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfFloat64(**x)
}
case reflect.String:
if fd.Kind() == protoreflect.BytesKind {
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).StringPtr()
if *x == nil {
return conv.Zero()
}
if len(**x) == 0 {
return protoreflect.ValueOfBytes(nil)
}
return protoreflect.ValueOfBytes([]byte(**x))
}
}
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).StringPtr()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfString(**x)
}
case reflect.Slice:
if fd.Kind() == protoreflect.StringKind {
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bytes()
if len(*x) == 0 {
return conv.Zero()
}
return protoreflect.ValueOfString(string(*x))
}
}
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bytes()
if *x == nil {
return conv.Zero()
}
return protoreflect.ValueOfBytes(*x)
}
}
panic("unexpected protobuf kind: " + ft.Kind().String())
}
func getterForDirectScalar(fd protoreflect.FieldDescriptor, fs reflect.StructField, conv Converter, fieldOffset offset) func(p pointer) protoreflect.Value {
ft := fs.Type
if fd.Kind() == protoreflect.EnumKind {
// Enums for non nullable types.
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
rv := p.Apply(fieldOffset).AsValueOf(fs.Type).Elem()
return conv.PBValueOf(rv)
}
}
switch ft.Kind() {
case reflect.Bool:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bool()
return protoreflect.ValueOfBool(*x)
}
case reflect.Int32:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Int32()
return protoreflect.ValueOfInt32(*x)
}
case reflect.Uint32:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Uint32()
return protoreflect.ValueOfUint32(*x)
}
case reflect.Int64:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Int64()
return protoreflect.ValueOfInt64(*x)
}
case reflect.Uint64:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Uint64()
return protoreflect.ValueOfUint64(*x)
}
case reflect.Float32:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Float32()
return protoreflect.ValueOfFloat32(*x)
}
case reflect.Float64:
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Float64()
return protoreflect.ValueOfFloat64(*x)
}
case reflect.String:
if fd.Kind() == protoreflect.BytesKind {
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).String()
if len(*x) == 0 {
return protoreflect.ValueOfBytes(nil)
}
return protoreflect.ValueOfBytes([]byte(*x))
}
}
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).String()
return protoreflect.ValueOfString(*x)
}
case reflect.Slice:
if fd.Kind() == protoreflect.StringKind {
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bytes()
return protoreflect.ValueOfString(string(*x))
}
}
return func(p pointer) protoreflect.Value {
if p.IsNil() {
return conv.Zero()
}
x := p.Apply(fieldOffset).Bytes()
return protoreflect.ValueOfBytes(*x)
}
}
panic("unexpected protobuf kind: " + ft.Kind().String())
}

View File

@ -8,6 +8,8 @@ import (
"reflect"
"sync/atomic"
"unsafe"
"google.golang.org/protobuf/internal/protolazy"
)
const UnsafeEnabled = true
@ -111,6 +113,13 @@ func (p pointer) BytesPtr() **[]byte { return (**[]byte)(p.p)
func (p pointer) BytesSlice() *[][]byte { return (*[][]byte)(p.p) }
func (p pointer) WeakFields() *weakFields { return (*weakFields)(p.p) }
func (p pointer) Extensions() *map[int32]ExtensionField { return (*map[int32]ExtensionField)(p.p) }
func (p pointer) LazyInfoPtr() **protolazy.XXX_lazyUnmarshalInfo {
return (**protolazy.XXX_lazyUnmarshalInfo)(p.p)
}
func (p pointer) PresenceInfo() presence {
return presence{P: p.p}
}
func (p pointer) Elem() pointer {
return pointer{p: *(*unsafe.Pointer)(p.p)}

View File

@ -0,0 +1,42 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"sync/atomic"
"unsafe"
)
func (p pointer) AtomicGetPointer() pointer {
return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))}
}
func (p pointer) AtomicSetPointer(v pointer) {
atomic.StorePointer((*unsafe.Pointer)(p.p), v.p)
}
func (p pointer) AtomicSetNilPointer() {
atomic.StorePointer((*unsafe.Pointer)(p.p), unsafe.Pointer(nil))
}
func (p pointer) AtomicSetPointerIfNil(v pointer) pointer {
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(p.p), unsafe.Pointer(nil), v.p) {
return v
}
return pointer{p: atomic.LoadPointer((*unsafe.Pointer)(p.p))}
}
type atomicV1MessageInfo struct{ p Pointer }
func (mi *atomicV1MessageInfo) Get() Pointer {
return Pointer(atomic.LoadPointer((*unsafe.Pointer)(&mi.p)))
}
func (mi *atomicV1MessageInfo) SetIfNil(p Pointer) Pointer {
if atomic.CompareAndSwapPointer((*unsafe.Pointer)(&mi.p), nil, unsafe.Pointer(p)) {
return p
}
return mi.Get()
}

View File

@ -0,0 +1,142 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package impl
import (
"sync/atomic"
"unsafe"
)
// presenceSize represents the size of a presence set, which should be the largest index of the set+1
type presenceSize uint32
// presence is the internal representation of the bitmap array in a generated protobuf
type presence struct {
// This is a pointer to the beginning of an array of uint32
P unsafe.Pointer
}
func (p presence) toElem(num uint32) (ret *uint32) {
const (
bitsPerByte = 8
siz = unsafe.Sizeof(*ret)
)
// p.P points to an array of uint32, num is the bit in this array that the
// caller wants to check/manipulate. Calculate the index in the array that
// contains this specific bit. E.g.: 76 / 32 = 2 (integer division).
offset := uintptr(num) / (siz * bitsPerByte) * siz
return (*uint32)(unsafe.Pointer(uintptr(p.P) + offset))
}
// Present checks for the presence of a specific field number in a presence set.
func (p presence) Present(num uint32) bool {
if p.P == nil {
return false
}
return Export{}.Present(p.toElem(num), num)
}
// SetPresent adds presence for a specific field number in a presence set.
func (p presence) SetPresent(num uint32, size presenceSize) {
Export{}.SetPresent(p.toElem(num), num, uint32(size))
}
// SetPresentUnatomic adds presence for a specific field number in a presence set without using
// atomic operations. Only to be called during unmarshaling.
func (p presence) SetPresentUnatomic(num uint32, size presenceSize) {
Export{}.SetPresentNonAtomic(p.toElem(num), num, uint32(size))
}
// ClearPresent removes presence for a specific field number in a presence set.
func (p presence) ClearPresent(num uint32) {
Export{}.ClearPresent(p.toElem(num), num)
}
// LoadPresenceCache (together with PresentInCache) allows for a
// cached version of checking for presence without re-reading the word
// for every field. It is optimized for efficiency and assumes no
// simltaneous mutation of the presence set (or at least does not have
// a problem with simultaneous mutation giving inconsistent results).
func (p presence) LoadPresenceCache() (current uint32) {
if p.P == nil {
return 0
}
return atomic.LoadUint32((*uint32)(p.P))
}
// PresentInCache reads presence from a cached word in the presence
// bitmap. It caches up a new word if the bit is outside the
// word. This is for really fast iteration through bitmaps in cases
// where we either know that the bitmap will not be altered, or we
// don't care about inconsistencies caused by simultaneous writes.
func (p presence) PresentInCache(num uint32, cachedElement *uint32, current *uint32) bool {
if num/32 != *cachedElement {
o := uintptr(num/32) * unsafe.Sizeof(uint32(0))
q := (*uint32)(unsafe.Pointer(uintptr(p.P) + o))
*current = atomic.LoadUint32(q)
*cachedElement = num / 32
}
return (*current & (1 << (num % 32))) > 0
}
// AnyPresent checks if any field is marked as present in the bitmap.
func (p presence) AnyPresent(size presenceSize) bool {
n := uintptr((size + 31) / 32)
for j := uintptr(0); j < n; j++ {
o := j * unsafe.Sizeof(uint32(0))
q := (*uint32)(unsafe.Pointer(uintptr(p.P) + o))
b := atomic.LoadUint32(q)
if b > 0 {
return true
}
}
return false
}
// toRaceDetectData finds the preceding RaceDetectHookData in a
// message by using pointer arithmetic. As the type of the presence
// set (bitmap) varies with the number of fields in the protobuf, we
// can not have a struct type containing the array and the
// RaceDetectHookData. instead the RaceDetectHookData is placed
// immediately before the bitmap array, and we find it by walking
// backwards in the struct.
//
// This method is only called from the race-detect version of the code,
// so RaceDetectHookData is never an empty struct.
func (p presence) toRaceDetectData() *RaceDetectHookData {
var template struct {
d RaceDetectHookData
a [1]uint32
}
o := (uintptr(unsafe.Pointer(&template.a)) - uintptr(unsafe.Pointer(&template.d)))
return (*RaceDetectHookData)(unsafe.Pointer(uintptr(p.P) - o))
}
func atomicLoadShadowPresence(p **[]byte) *[]byte {
return (*[]byte)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
}
func atomicStoreShadowPresence(p **[]byte, v *[]byte) {
atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(p)), nil, unsafe.Pointer(v))
}
// findPointerToRaceDetectData finds the preceding RaceDetectHookData
// in a message by using pointer arithmetic. For the methods called
// directy from generated code, we don't have a pointer to the
// beginning of the presence set, but a pointer inside the array. As
// we know the index of the bit we're manipulating (num), we can
// calculate which element of the array ptr is pointing to. With that
// information we find the preceding RaceDetectHookData and can
// manipulate the shadow bitmap.
//
// This method is only called from the race-detect version of the
// code, so RaceDetectHookData is never an empty struct.
func findPointerToRaceDetectData(ptr *uint32, num uint32) *RaceDetectHookData {
var template struct {
d RaceDetectHookData
a [1]uint32
}
o := (uintptr(unsafe.Pointer(&template.a)) - uintptr(unsafe.Pointer(&template.d))) + uintptr(num/32)*unsafe.Sizeof(uint32(0))
return (*RaceDetectHookData)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) - o))
}

View File

@ -37,6 +37,10 @@ const (
// ValidationValid indicates that unmarshaling the message will succeed.
ValidationValid
// ValidationWrongWireType indicates that a validated field does not have
// the expected wire type.
ValidationWrongWireType
)
func (v ValidationStatus) String() string {
@ -149,11 +153,23 @@ func newValidationInfo(fd protoreflect.FieldDescriptor, ft reflect.Type) validat
switch fd.Kind() {
case protoreflect.MessageKind:
vi.typ = validationTypeMessage
if ft.Kind() == reflect.Ptr {
// Repeated opaque message fields are *[]*T.
ft = ft.Elem()
}
if ft.Kind() == reflect.Slice {
vi.mi = getMessageInfo(ft.Elem())
}
case protoreflect.GroupKind:
vi.typ = validationTypeGroup
if ft.Kind() == reflect.Ptr {
// Repeated opaque message fields are *[]*T.
ft = ft.Elem()
}
if ft.Kind() == reflect.Slice {
vi.mi = getMessageInfo(ft.Elem())
}

View File

@ -0,0 +1,364 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Helper code for parsing a protocol buffer
package protolazy
import (
"errors"
"fmt"
"io"
"google.golang.org/protobuf/encoding/protowire"
)
// BufferReader is a structure encapsulating a protobuf and a current position
type BufferReader struct {
Buf []byte
Pos int
}
// NewBufferReader creates a new BufferRead from a protobuf
func NewBufferReader(buf []byte) BufferReader {
return BufferReader{Buf: buf, Pos: 0}
}
var errOutOfBounds = errors.New("protobuf decoding: out of bounds")
var errOverflow = errors.New("proto: integer overflow")
func (b *BufferReader) DecodeVarintSlow() (x uint64, err error) {
i := b.Pos
l := len(b.Buf)
for shift := uint(0); shift < 64; shift += 7 {
if i >= l {
err = io.ErrUnexpectedEOF
return
}
v := b.Buf[i]
i++
x |= (uint64(v) & 0x7F) << shift
if v < 0x80 {
b.Pos = i
return
}
}
// The number is too large to represent in a 64-bit value.
err = errOverflow
return
}
// decodeVarint decodes a varint at the current position
func (b *BufferReader) DecodeVarint() (x uint64, err error) {
i := b.Pos
buf := b.Buf
if i >= len(buf) {
return 0, io.ErrUnexpectedEOF
} else if buf[i] < 0x80 {
b.Pos++
return uint64(buf[i]), nil
} else if len(buf)-i < 10 {
return b.DecodeVarintSlow()
}
var v uint64
// we already checked the first byte
x = uint64(buf[i]) & 127
i++
v = uint64(buf[i])
i++
x |= (v & 127) << 7
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 14
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 21
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 28
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 35
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 42
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 49
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 56
if v < 128 {
goto done
}
v = uint64(buf[i])
i++
x |= (v & 127) << 63
if v < 128 {
goto done
}
return 0, errOverflow
done:
b.Pos = i
return
}
// decodeVarint32 decodes a varint32 at the current position
func (b *BufferReader) DecodeVarint32() (x uint32, err error) {
i := b.Pos
buf := b.Buf
if i >= len(buf) {
return 0, io.ErrUnexpectedEOF
} else if buf[i] < 0x80 {
b.Pos++
return uint32(buf[i]), nil
} else if len(buf)-i < 5 {
v, err := b.DecodeVarintSlow()
return uint32(v), err
}
var v uint32
// we already checked the first byte
x = uint32(buf[i]) & 127
i++
v = uint32(buf[i])
i++
x |= (v & 127) << 7
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
x |= (v & 127) << 14
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
x |= (v & 127) << 21
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
x |= (v & 127) << 28
if v < 128 {
goto done
}
return 0, errOverflow
done:
b.Pos = i
return
}
// skipValue skips a value in the protobuf, based on the specified tag
func (b *BufferReader) SkipValue(tag uint32) (err error) {
wireType := tag & 0x7
switch protowire.Type(wireType) {
case protowire.VarintType:
err = b.SkipVarint()
case protowire.Fixed64Type:
err = b.SkipFixed64()
case protowire.BytesType:
var n uint32
n, err = b.DecodeVarint32()
if err == nil {
err = b.Skip(int(n))
}
case protowire.StartGroupType:
err = b.SkipGroup(tag)
case protowire.Fixed32Type:
err = b.SkipFixed32()
default:
err = fmt.Errorf("Unexpected wire type (%d)", wireType)
}
return
}
// skipGroup skips a group with the specified tag. It executes efficiently using a tag stack
func (b *BufferReader) SkipGroup(tag uint32) (err error) {
tagStack := make([]uint32, 0, 16)
tagStack = append(tagStack, tag)
var n uint32
for len(tagStack) > 0 {
tag, err = b.DecodeVarint32()
if err != nil {
return err
}
switch protowire.Type(tag & 0x7) {
case protowire.VarintType:
err = b.SkipVarint()
case protowire.Fixed64Type:
err = b.Skip(8)
case protowire.BytesType:
n, err = b.DecodeVarint32()
if err == nil {
err = b.Skip(int(n))
}
case protowire.StartGroupType:
tagStack = append(tagStack, tag)
case protowire.Fixed32Type:
err = b.SkipFixed32()
case protowire.EndGroupType:
if protoFieldNumber(tagStack[len(tagStack)-1]) == protoFieldNumber(tag) {
tagStack = tagStack[:len(tagStack)-1]
} else {
err = fmt.Errorf("end group tag %d does not match begin group tag %d at pos %d",
protoFieldNumber(tag), protoFieldNumber(tagStack[len(tagStack)-1]), b.Pos)
}
}
if err != nil {
return err
}
}
return nil
}
// skipVarint effiently skips a varint
func (b *BufferReader) SkipVarint() (err error) {
i := b.Pos
if len(b.Buf)-i < 10 {
// Use DecodeVarintSlow() to check for buffer overflow, but ignore result
if _, err := b.DecodeVarintSlow(); err != nil {
return err
}
return nil
}
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
i++
if b.Buf[i] < 0x80 {
goto out
}
return errOverflow
out:
b.Pos = i + 1
return nil
}
// skip skips the specified number of bytes
func (b *BufferReader) Skip(n int) (err error) {
if len(b.Buf) < b.Pos+n {
return io.ErrUnexpectedEOF
}
b.Pos += n
return
}
// skipFixed64 skips a fixed64
func (b *BufferReader) SkipFixed64() (err error) {
return b.Skip(8)
}
// skipFixed32 skips a fixed32
func (b *BufferReader) SkipFixed32() (err error) {
return b.Skip(4)
}
// skipBytes skips a set of bytes
func (b *BufferReader) SkipBytes() (err error) {
n, err := b.DecodeVarint32()
if err != nil {
return err
}
return b.Skip(int(n))
}
// Done returns whether we are at the end of the protobuf
func (b *BufferReader) Done() bool {
return b.Pos == len(b.Buf)
}
// Remaining returns how many bytes remain
func (b *BufferReader) Remaining() int {
return len(b.Buf) - b.Pos
}

View File

@ -0,0 +1,359 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package protolazy contains internal data structures for lazy message decoding.
package protolazy
import (
"fmt"
"sort"
"google.golang.org/protobuf/encoding/protowire"
piface "google.golang.org/protobuf/runtime/protoiface"
)
// IndexEntry is the structure for an index of the fields in a message of a
// proto (not descending to sub-messages)
type IndexEntry struct {
FieldNum uint32
// first byte of this tag/field
Start uint32
// first byte after a contiguous sequence of bytes for this tag/field, which could
// include a single encoding of the field, or multiple encodings for the field
End uint32
// True if this protobuf segment includes multiple encodings of the field
MultipleContiguous bool
}
// XXX_lazyUnmarshalInfo has information about a particular lazily decoded message
//
// Deprecated: Do not use. This will be deleted in the near future.
type XXX_lazyUnmarshalInfo struct {
// Index of fields and their positions in the protobuf for this
// message. Make index be a pointer to a slice so it can be updated
// atomically. The index pointer is only set once (lazily when/if
// the index is first needed), and must always be SET and LOADED
// ATOMICALLY.
index *[]IndexEntry
// The protobuf associated with this lazily decoded message. It is
// only set during proto.Unmarshal(). It doesn't need to be set and
// loaded atomically, since any simultaneous set (Unmarshal) and read
// (during a get) would already be a race in the app code.
Protobuf []byte
// The flags present when Unmarshal was originally called for this particular message
unmarshalFlags piface.UnmarshalInputFlags
}
// The Buffer and SetBuffer methods let v2/internal/impl interact with
// XXX_lazyUnmarshalInfo via an interface, to avoid an import cycle.
// Buffer returns the lazy unmarshal buffer.
//
// Deprecated: Do not use. This will be deleted in the near future.
func (lazy *XXX_lazyUnmarshalInfo) Buffer() []byte {
return lazy.Protobuf
}
// SetBuffer sets the lazy unmarshal buffer.
//
// Deprecated: Do not use. This will be deleted in the near future.
func (lazy *XXX_lazyUnmarshalInfo) SetBuffer(b []byte) {
lazy.Protobuf = b
}
// SetUnmarshalFlags is called to set a copy of the original unmarshalInputFlags.
// The flags should reflect how Unmarshal was called.
func (lazy *XXX_lazyUnmarshalInfo) SetUnmarshalFlags(f piface.UnmarshalInputFlags) {
lazy.unmarshalFlags = f
}
// UnmarshalFlags returns the original unmarshalInputFlags.
func (lazy *XXX_lazyUnmarshalInfo) UnmarshalFlags() piface.UnmarshalInputFlags {
return lazy.unmarshalFlags
}
// AllowedPartial returns true if the user originally unmarshalled this message with
// AllowPartial set to true
func (lazy *XXX_lazyUnmarshalInfo) AllowedPartial() bool {
return (lazy.unmarshalFlags & piface.UnmarshalCheckRequired) == 0
}
func protoFieldNumber(tag uint32) uint32 {
return tag >> 3
}
// buildIndex builds an index of the specified protobuf, return the index
// array and an error.
func buildIndex(buf []byte) ([]IndexEntry, error) {
index := make([]IndexEntry, 0, 16)
var lastProtoFieldNum uint32
var outOfOrder bool
var r BufferReader = NewBufferReader(buf)
for !r.Done() {
var tag uint32
var err error
var curPos = r.Pos
// INLINED: tag, err = r.DecodeVarint32()
{
i := r.Pos
buf := r.Buf
if i >= len(buf) {
return nil, errOutOfBounds
} else if buf[i] < 0x80 {
r.Pos++
tag = uint32(buf[i])
} else if r.Remaining() < 5 {
var v uint64
v, err = r.DecodeVarintSlow()
tag = uint32(v)
} else {
var v uint32
// we already checked the first byte
tag = uint32(buf[i]) & 127
i++
v = uint32(buf[i])
i++
tag |= (v & 127) << 7
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
tag |= (v & 127) << 14
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
tag |= (v & 127) << 21
if v < 128 {
goto done
}
v = uint32(buf[i])
i++
tag |= (v & 127) << 28
if v < 128 {
goto done
}
return nil, errOutOfBounds
done:
r.Pos = i
}
}
// DONE: tag, err = r.DecodeVarint32()
fieldNum := protoFieldNumber(tag)
if fieldNum < lastProtoFieldNum {
outOfOrder = true
}
// Skip the current value -- will skip over an entire group as well.
// INLINED: err = r.SkipValue(tag)
wireType := tag & 0x7
switch protowire.Type(wireType) {
case protowire.VarintType:
// INLINED: err = r.SkipVarint()
i := r.Pos
if len(r.Buf)-i < 10 {
// Use DecodeVarintSlow() to skip while
// checking for buffer overflow, but ignore result
_, err = r.DecodeVarintSlow()
goto out2
}
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
i++
if r.Buf[i] < 0x80 {
goto out
}
return nil, errOverflow
out:
r.Pos = i + 1
// DONE: err = r.SkipVarint()
case protowire.Fixed64Type:
err = r.SkipFixed64()
case protowire.BytesType:
var n uint32
n, err = r.DecodeVarint32()
if err == nil {
err = r.Skip(int(n))
}
case protowire.StartGroupType:
err = r.SkipGroup(tag)
case protowire.Fixed32Type:
err = r.SkipFixed32()
default:
err = fmt.Errorf("Unexpected wire type (%d)", wireType)
}
// DONE: err = r.SkipValue(tag)
out2:
if err != nil {
return nil, err
}
if fieldNum != lastProtoFieldNum {
index = append(index, IndexEntry{FieldNum: fieldNum,
Start: uint32(curPos),
End: uint32(r.Pos)},
)
} else {
index[len(index)-1].End = uint32(r.Pos)
index[len(index)-1].MultipleContiguous = true
}
lastProtoFieldNum = fieldNum
}
if outOfOrder {
sort.Slice(index, func(i, j int) bool {
return index[i].FieldNum < index[j].FieldNum ||
(index[i].FieldNum == index[j].FieldNum &&
index[i].Start < index[j].Start)
})
}
return index, nil
}
func (lazy *XXX_lazyUnmarshalInfo) SizeField(num uint32) (size int) {
start, end, found, _, multipleEntries := lazy.FindFieldInProto(num)
if multipleEntries != nil {
for _, entry := range multipleEntries {
size += int(entry.End - entry.Start)
}
return size
}
if !found {
return 0
}
return int(end - start)
}
func (lazy *XXX_lazyUnmarshalInfo) AppendField(b []byte, num uint32) ([]byte, bool) {
start, end, found, _, multipleEntries := lazy.FindFieldInProto(num)
if multipleEntries != nil {
for _, entry := range multipleEntries {
b = append(b, lazy.Protobuf[entry.Start:entry.End]...)
}
return b, true
}
if !found {
return nil, false
}
b = append(b, lazy.Protobuf[start:end]...)
return b, true
}
func (lazy *XXX_lazyUnmarshalInfo) SetIndex(index []IndexEntry) {
atomicStoreIndex(&lazy.index, &index)
}
// FindFieldInProto looks for field fieldNum in lazyUnmarshalInfo information
// (including protobuf), returns startOffset/endOffset/found.
func (lazy *XXX_lazyUnmarshalInfo) FindFieldInProto(fieldNum uint32) (start, end uint32, found, multipleContiguous bool, multipleEntries []IndexEntry) {
if lazy.Protobuf == nil {
// There is no backing protobuf for this message -- it was made from a builder
return 0, 0, false, false, nil
}
index := atomicLoadIndex(&lazy.index)
if index == nil {
r, err := buildIndex(lazy.Protobuf)
if err != nil {
panic(fmt.Sprintf("findFieldInfo: error building index when looking for field %d: %v", fieldNum, err))
}
// lazy.index is a pointer to the slice returned by BuildIndex
index = &r
atomicStoreIndex(&lazy.index, index)
}
return lookupField(index, fieldNum)
}
// lookupField returns the offset at which the indicated field starts using
// the index, offset immediately after field ends (including all instances of
// a repeated field), and bools indicating if field was found and if there
// are multiple encodings of the field in the byte range.
//
// To hande the uncommon case where there are repeated encodings for the same
// field which are not consecutive in the protobuf (so we need to returns
// multiple start/end offsets), we also return a slice multipleEntries. If
// multipleEntries is non-nil, then multiple entries were found, and the
// values in the slice should be used, rather than start/end/found.
func lookupField(indexp *[]IndexEntry, fieldNum uint32) (start, end uint32, found bool, multipleContiguous bool, multipleEntries []IndexEntry) {
// The pointer indexp to the index was already loaded atomically.
// The slice is uniquely associated with the pointer, so it doesn't
// need to be loaded atomically.
index := *indexp
for i, entry := range index {
if fieldNum == entry.FieldNum {
if i < len(index)-1 && entry.FieldNum == index[i+1].FieldNum {
// Handle the uncommon case where there are
// repeated entries for the same field which
// are not contiguous in the protobuf.
multiple := make([]IndexEntry, 1, 2)
multiple[0] = IndexEntry{fieldNum, entry.Start, entry.End, entry.MultipleContiguous}
i++
for i < len(index) && index[i].FieldNum == fieldNum {
multiple = append(multiple, IndexEntry{fieldNum, index[i].Start, index[i].End, index[i].MultipleContiguous})
i++
}
return 0, 0, false, false, multiple
}
return entry.Start, entry.End, true, entry.MultipleContiguous, nil
}
if fieldNum < entry.FieldNum {
return 0, 0, false, false, nil
}
}
return 0, 0, false, false, nil
}

View File

@ -0,0 +1,17 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package protolazy
import (
"sync/atomic"
"unsafe"
)
func atomicLoadIndex(p **[]IndexEntry) *[]IndexEntry {
return (*[]IndexEntry)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(p))))
}
func atomicStoreIndex(p **[]IndexEntry, v *[]IndexEntry) {
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(p)), unsafe.Pointer(v))
}

View File

@ -51,8 +51,8 @@ import (
// 10. Send out the CL for review and submit it.
const (
Major = 1
Minor = 35
Patch = 2
Minor = 36
Patch = 1
PreRelease = ""
)

View File

@ -47,6 +47,12 @@ type UnmarshalOptions struct {
// RecursionLimit limits how deeply messages may be nested.
// If zero, a default limit is applied.
RecursionLimit int
//
// NoLazyDecoding turns off lazy decoding, which otherwise is enabled by
// default. Lazy decoding only affects submessages (annotated with [lazy =
// true] in the .proto file) within messages that use the Opaque API.
NoLazyDecoding bool
}
// Unmarshal parses the wire-format message in b and places the result in m.
@ -104,6 +110,16 @@ func (o UnmarshalOptions) unmarshal(b []byte, m protoreflect.Message) (out proto
if o.DiscardUnknown {
in.Flags |= protoiface.UnmarshalDiscardUnknown
}
if !allowPartial {
// This does not affect how current unmarshal functions work, it just allows them
// to record this for lazy the decoding case.
in.Flags |= protoiface.UnmarshalCheckRequired
}
if o.NoLazyDecoding {
in.Flags |= protoiface.UnmarshalNoLazyDecoding
}
out, err = methods.Unmarshal(in)
} else {
o.RecursionLimit--

View File

@ -63,7 +63,8 @@ type MarshalOptions struct {
// options (except for UseCachedSize itself).
//
// 2. The message and all its submessages have not changed in any
// way since the Size call.
// way since the Size call. For lazily decoded messages, accessing
// a message results in decoding the message, which is a change.
//
// If either of these invariants is violated,
// the results are undefined and may include panics or corrupted output.

View File

@ -12,11 +12,19 @@ import (
)
// Size returns the size in bytes of the wire-format encoding of m.
//
// Note that Size might return more bytes than Marshal will write in the case of
// lazily decoded messages that arrive in non-minimal wire format: see
// https://protobuf.dev/reference/go/size/ for more details.
func Size(m Message) int {
return MarshalOptions{}.Size(m)
}
// Size returns the size in bytes of the wire-format encoding of m.
//
// Note that Size might return more bytes than Marshal will write in the case of
// lazily decoded messages that arrive in non-minimal wire format: see
// https://protobuf.dev/reference/go/size/ for more details.
func (o MarshalOptions) Size(m Message) int {
// Treat a nil message interface as an empty message; nothing to output.
if m == nil {

View File

@ -0,0 +1,80 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package proto
// ValueOrNil returns nil if has is false, or a pointer to a new variable
// containing the value returned by the specified getter.
//
// This function is similar to the wrappers (proto.Int32(), proto.String(),
// etc.), but is generic (works for any field type) and works with the hasser
// and getter of a field, as opposed to a value.
//
// This is convenient when populating builder fields.
//
// Example:
//
// hop := attr.GetDirectHop()
// injectedRoute := ripb.InjectedRoute_builder{
// Prefixes: route.GetPrefixes(),
// NextHop: proto.ValueOrNil(hop.HasAddress(), hop.GetAddress),
// }
func ValueOrNil[T any](has bool, getter func() T) *T {
if !has {
return nil
}
v := getter()
return &v
}
// ValueOrDefault returns the protobuf message val if val is not nil, otherwise
// it returns a pointer to an empty val message.
//
// This function allows for translating code from the old Open Struct API to the
// new Opaque API.
//
// The old Open Struct API represented oneof fields with a wrapper struct:
//
// var signedImg *accountpb.SignedImage
// profile := &accountpb.Profile{
// // The Avatar oneof will be set, with an empty SignedImage.
// Avatar: &accountpb.Profile_SignedImage{signedImg},
// }
//
// The new Opaque API treats oneof fields like regular fields, there are no more
// wrapper structs:
//
// var signedImg *accountpb.SignedImage
// profile := &accountpb.Profile{}
// profile.SetSignedImage(signedImg)
//
// For convenience, the Opaque API also offers Builders, which allow for a
// direct translation of struct initialization. However, because Builders use
// nilness to represent field presence (but there is no non-nil wrapper struct
// anymore), Builders cannot distinguish between an unset oneof and a set oneof
// with nil message. The above code would need to be translated with help of the
// ValueOrDefault function to retain the same behavior:
//
// var signedImg *accountpb.SignedImage
// return &accountpb.Profile_builder{
// SignedImage: proto.ValueOrDefault(signedImg),
// }.Build()
func ValueOrDefault[T interface {
*P
Message
}, P any](val T) T {
if val == nil {
return T(new(P))
}
return val
}
// ValueOrDefaultBytes is like ValueOrDefault but for working with fields of
// type []byte.
func ValueOrDefaultBytes(val []byte) []byte {
if val == nil {
return []byte{}
}
return val
}

View File

@ -152,7 +152,7 @@ type Message interface {
// This method may return nil.
//
// The returned methods type is identical to
// google.golang.org/protobuf/runtime/protoiface.Methods.
// [google.golang.org/protobuf/runtime/protoiface.Methods].
// Consult the protoiface package documentation for details.
ProtoMethods() *methods
}

View File

@ -122,6 +122,22 @@ type UnmarshalInputFlags = uint8
const (
UnmarshalDiscardUnknown UnmarshalInputFlags = 1 << iota
// UnmarshalAliasBuffer permits unmarshal operations to alias the input buffer.
// The unmarshaller must not modify the contents of the buffer.
UnmarshalAliasBuffer
// UnmarshalValidated indicates that validation has already been
// performed on the input buffer.
UnmarshalValidated
// UnmarshalCheckRequired is set if this unmarshal operation ultimately will care if required fields are
// initialized.
UnmarshalCheckRequired
// UnmarshalNoLazyDecoding is set if this unmarshal operation should not use
// lazy decoding, even when otherwise available.
UnmarshalNoLazyDecoding
)
// UnmarshalOutputFlags are output from the Unmarshal method.

View File

@ -15,6 +15,7 @@ import (
"google.golang.org/protobuf/internal/filedesc"
"google.golang.org/protobuf/internal/filetype"
"google.golang.org/protobuf/internal/impl"
"google.golang.org/protobuf/internal/protolazy"
)
// UnsafeEnabled specifies whether package unsafe can be used.
@ -39,6 +40,9 @@ type (
ExtensionFieldV1 = impl.ExtensionField
Pointer = impl.Pointer
LazyUnmarshalInfo = *protolazy.XXX_lazyUnmarshalInfo
RaceDetectHookData = impl.RaceDetectHookData
)
var X impl.Export

View File

@ -210,10 +210,7 @@ import (
// "value": "1.212s"
// }
type Any struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// A URL/resource name that uniquely identifies the type of the serialized
// protocol buffer message. This string must contain at least
// one "/" character. The last segment of the URL's path must represent
@ -244,7 +241,9 @@ type Any struct {
// used with implementation specific semantics.
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
// Must be a valid serialized protocol buffer of the above specified type.
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// New marshals src into a new Any instance.

View File

@ -141,10 +141,7 @@ import (
// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
// microsecond should be expressed in JSON format as "3.000001s".
type Duration struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// Signed seconds of the span of time. Must be from -315,576,000,000
// to +315,576,000,000 inclusive. Note: these bounds are computed from:
// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
@ -155,7 +152,9 @@ type Duration struct {
// of one second or more, a non-zero value for the `nanos` field must be
// of the same sign as the `seconds` field. Must be from -999,999,999
// to +999,999,999 inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// New constructs a new Duration from the provided time.Duration.

View File

@ -284,12 +284,11 @@ import (
// request should verify the included field paths, and return an
// `INVALID_ARGUMENT` error if any path is unmappable.
type FieldMask struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The set of field mask paths.
Paths []string `protobuf:"bytes,1,rep,name=paths,proto3" json:"paths,omitempty"`
Paths []string `protobuf:"bytes,1,rep,name=paths,proto3" json:"paths,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// New constructs a field mask from a list of paths and verifies that

View File

@ -187,12 +187,11 @@ func (NullValue) EnumDescriptor() ([]byte, []int) {
//
// The JSON representation for `Struct` is JSON object.
type Struct struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// Unordered map of dynamically typed values.
Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Fields map[string]*Value `protobuf:"bytes,1,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// NewStruct constructs a Struct from a general-purpose Go map.
@ -276,13 +275,10 @@ func (x *Struct) GetFields() map[string]*Value {
//
// The JSON representation for `Value` is JSON value.
type Value struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The kind of value.
//
// Types that are assignable to Kind:
// Types that are valid to be assigned to Kind:
//
// *Value_NullValue
// *Value_NumberValue
@ -290,7 +286,9 @@ type Value struct {
// *Value_BoolValue
// *Value_StructValue
// *Value_ListValue
Kind isValue_Kind `protobuf_oneof:"kind"`
Kind isValue_Kind `protobuf_oneof:"kind"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// NewValue constructs a Value from a general-purpose Go interface.
@ -483,51 +481,63 @@ func (*Value) Descriptor() ([]byte, []int) {
return file_google_protobuf_struct_proto_rawDescGZIP(), []int{1}
}
func (m *Value) GetKind() isValue_Kind {
if m != nil {
return m.Kind
func (x *Value) GetKind() isValue_Kind {
if x != nil {
return x.Kind
}
return nil
}
func (x *Value) GetNullValue() NullValue {
if x, ok := x.GetKind().(*Value_NullValue); ok {
return x.NullValue
if x != nil {
if x, ok := x.Kind.(*Value_NullValue); ok {
return x.NullValue
}
}
return NullValue_NULL_VALUE
}
func (x *Value) GetNumberValue() float64 {
if x, ok := x.GetKind().(*Value_NumberValue); ok {
return x.NumberValue
if x != nil {
if x, ok := x.Kind.(*Value_NumberValue); ok {
return x.NumberValue
}
}
return 0
}
func (x *Value) GetStringValue() string {
if x, ok := x.GetKind().(*Value_StringValue); ok {
return x.StringValue
if x != nil {
if x, ok := x.Kind.(*Value_StringValue); ok {
return x.StringValue
}
}
return ""
}
func (x *Value) GetBoolValue() bool {
if x, ok := x.GetKind().(*Value_BoolValue); ok {
return x.BoolValue
if x != nil {
if x, ok := x.Kind.(*Value_BoolValue); ok {
return x.BoolValue
}
}
return false
}
func (x *Value) GetStructValue() *Struct {
if x, ok := x.GetKind().(*Value_StructValue); ok {
return x.StructValue
if x != nil {
if x, ok := x.Kind.(*Value_StructValue); ok {
return x.StructValue
}
}
return nil
}
func (x *Value) GetListValue() *ListValue {
if x, ok := x.GetKind().(*Value_ListValue); ok {
return x.ListValue
if x != nil {
if x, ok := x.Kind.(*Value_ListValue); ok {
return x.ListValue
}
}
return nil
}
@ -582,12 +592,11 @@ func (*Value_ListValue) isValue_Kind() {}
//
// The JSON representation for `ListValue` is JSON array.
type ListValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// Repeated field of dynamically typed values.
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
Values []*Value `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// NewList constructs a ListValue from a general-purpose Go slice.

View File

@ -170,10 +170,7 @@ import (
// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()
// ) to obtain a formatter capable of generating timestamps in this format.
type Timestamp struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
@ -182,7 +179,9 @@ type Timestamp struct {
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Now constructs a new Timestamp from the current time.

View File

@ -54,12 +54,11 @@ import (
//
// The JSON representation for `DoubleValue` is JSON number.
type DoubleValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The double value.
Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"`
Value float64 `protobuf:"fixed64,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Double stores v in a new DoubleValue and returns a pointer to it.
@ -108,12 +107,11 @@ func (x *DoubleValue) GetValue() float64 {
//
// The JSON representation for `FloatValue` is JSON number.
type FloatValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The float value.
Value float32 `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"`
Value float32 `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Float stores v in a new FloatValue and returns a pointer to it.
@ -162,12 +160,11 @@ func (x *FloatValue) GetValue() float32 {
//
// The JSON representation for `Int64Value` is JSON string.
type Int64Value struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The int64 value.
Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Int64 stores v in a new Int64Value and returns a pointer to it.
@ -216,12 +213,11 @@ func (x *Int64Value) GetValue() int64 {
//
// The JSON representation for `UInt64Value` is JSON string.
type UInt64Value struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The uint64 value.
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// UInt64 stores v in a new UInt64Value and returns a pointer to it.
@ -270,12 +266,11 @@ func (x *UInt64Value) GetValue() uint64 {
//
// The JSON representation for `Int32Value` is JSON number.
type Int32Value struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The int32 value.
Value int32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
Value int32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Int32 stores v in a new Int32Value and returns a pointer to it.
@ -324,12 +319,11 @@ func (x *Int32Value) GetValue() int32 {
//
// The JSON representation for `UInt32Value` is JSON number.
type UInt32Value struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The uint32 value.
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
Value uint32 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// UInt32 stores v in a new UInt32Value and returns a pointer to it.
@ -378,12 +372,11 @@ func (x *UInt32Value) GetValue() uint32 {
//
// The JSON representation for `BoolValue` is JSON `true` and `false`.
type BoolValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The bool value.
Value bool `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
Value bool `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Bool stores v in a new BoolValue and returns a pointer to it.
@ -432,12 +425,11 @@ func (x *BoolValue) GetValue() bool {
//
// The JSON representation for `StringValue` is JSON string.
type StringValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The string value.
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// String stores v in a new StringValue and returns a pointer to it.
@ -486,12 +478,11 @@ func (x *StringValue) GetValue() string {
//
// The JSON representation for `BytesValue` is JSON string.
type BytesValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
// The bytes value.
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Bytes stores v in a new BytesValue and returns a pointer to it.