vendor: update buildkit to v0.32.0-rc1
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
+2
-2
@@ -60,7 +60,7 @@ func Register(b Builder) {
|
||||
if !envconfig.CaseSensitiveBalancerRegistries {
|
||||
name = strings.ToLower(name)
|
||||
if name != b.Name() {
|
||||
logger.Warningf("Balancer registered with name %q. grpc-go will be switching to case sensitive balancer registries soon. After 2 releases, we will enable the env var by default.", b.Name())
|
||||
logger.Warningf("Balancer registered with name %q. grpc-go has switched to case sensitive balancer registries. GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES env variable will be removed in release v1.82.0", b.Name())
|
||||
}
|
||||
}
|
||||
m[name] = b
|
||||
@@ -85,7 +85,7 @@ func Get(name string) Builder {
|
||||
if !envconfig.CaseSensitiveBalancerRegistries {
|
||||
lowerName := strings.ToLower(name)
|
||||
if lowerName != name {
|
||||
logger.Warningf("Balancer retrieved for name %q. grpc-go will be switching to case sensitive balancer registries soon. After 2 releases, we will enable the env var by default.", name)
|
||||
logger.Warningf("Balancer retrieved for name %q. grpc-go has switched to case sensitive balancer registries. GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES env variable will be removed in release v1.82.0", name)
|
||||
}
|
||||
name = lowerName
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,9 +35,9 @@ import (
|
||||
"google.golang.org/grpc/balancer"
|
||||
"google.golang.org/grpc/balancer/pickfirst/internal"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/experimental/balancer/weight"
|
||||
expstats "google.golang.org/grpc/experimental/stats"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/internal/balancer/weight"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
internalgrpclog "google.golang.org/grpc/internal/grpclog"
|
||||
"google.golang.org/grpc/internal/pretty"
|
||||
|
||||
+18
-4
@@ -173,10 +173,8 @@ func newJoinDialOption(opts ...DialOption) DialOption {
|
||||
// If this option is set to true every connection will release the buffer after
|
||||
// flushing the data on the wire.
|
||||
//
|
||||
// # Experimental
|
||||
//
|
||||
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||
// later release.
|
||||
// Deprecated: shared write buffer is enabled by default. WithSharedWriteBuffer
|
||||
// will be removed in a future release.
|
||||
func WithSharedWriteBuffer(val bool) DialOption {
|
||||
return newFuncDialOption(func(o *dialOptions) {
|
||||
o.copts.SharedWriteBuffer = val
|
||||
@@ -229,6 +227,14 @@ func WithInitialConnWindowSize(s int32) DialOption {
|
||||
|
||||
// WithStaticStreamWindowSize returns a DialOption which sets the initial
|
||||
// stream window size to the value provided and disables dynamic flow control.
|
||||
//
|
||||
// Note that this also disables dynamic flow control for the connection,
|
||||
// falling back to a default static connection-level window of 64KB. To
|
||||
// use a larger connection-level window, you must also use the
|
||||
// [WithStaticConnWindowSize] DialOption.
|
||||
//
|
||||
// Most users should not configure static flow control windows unless
|
||||
// operating in a memory-constrained environment.
|
||||
func WithStaticStreamWindowSize(s int32) DialOption {
|
||||
return newFuncDialOption(func(o *dialOptions) {
|
||||
o.copts.InitialWindowSize = s
|
||||
@@ -239,6 +245,14 @@ func WithStaticStreamWindowSize(s int32) DialOption {
|
||||
// WithStaticConnWindowSize returns a DialOption which sets the initial
|
||||
// connection window size to the value provided and disables dynamic flow
|
||||
// control.
|
||||
//
|
||||
// Note that this also disables dynamic flow control for individual streams,
|
||||
// falling back to a default static connection-level window of 64KB. To
|
||||
// explicitly configure the stream-level window size, you must also use the
|
||||
// [WithStaticStreamWindowSize] DialOption.
|
||||
//
|
||||
// Most users should not configure static flow control windows unless
|
||||
// operating in a memory-constrained environment.
|
||||
func WithStaticConnWindowSize(s int32) DialOption {
|
||||
return newFuncDialOption(func(o *dialOptions) {
|
||||
o.copts.InitialConnWindowSize = s
|
||||
|
||||
+3
@@ -66,6 +66,9 @@ type Compressor interface {
|
||||
// Decompress reads data from r, decompresses it, and provides the
|
||||
// uncompressed data via the returned io.Reader. If an error occurs while
|
||||
// initializing the decompressor, that error is returned instead.
|
||||
//
|
||||
// The returned io.Reader may optionally implement io.ReadCloser, and if it
|
||||
// does, gRPC will call Close() exactly once.
|
||||
Decompress(r io.Reader) (io.Reader, error)
|
||||
// Name is the name of the compression codec and is used to set the content
|
||||
// coding header. The result must be static; the result cannot change
|
||||
|
||||
+9
-5
@@ -81,6 +81,8 @@ func (z *writer) Close() error {
|
||||
return z.Writer.Close()
|
||||
}
|
||||
|
||||
var _ io.Closer = &reader{}
|
||||
|
||||
type reader struct {
|
||||
*gzip.Reader
|
||||
pool *sync.Pool
|
||||
@@ -102,14 +104,16 @@ func (c *compressor) Decompress(r io.Reader) (io.Reader, error) {
|
||||
return z, nil
|
||||
}
|
||||
|
||||
func (z *reader) Read(p []byte) (n int, err error) {
|
||||
n, err = z.Reader.Read(p)
|
||||
if err == io.EOF {
|
||||
z.pool.Put(z)
|
||||
}
|
||||
func (r *reader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *reader) Close() error {
|
||||
defer r.pool.Put(r)
|
||||
return r.Reader.Close()
|
||||
}
|
||||
|
||||
func (c *compressor) Name() string {
|
||||
return Name
|
||||
}
|
||||
|
||||
Generated
Vendored
+15
-21
@@ -16,23 +16,23 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// Package weight contains utilities to manage endpoint weights. Weights are
|
||||
// used by LB policies such as ringhash to distribute load across multiple
|
||||
// endpoints.
|
||||
// Package weight contains utilities to manage endpoint weights.
|
||||
// Weights may be used by LB policies to distribute load across
|
||||
// multiple endpoints.
|
||||
//
|
||||
// # Experimental
|
||||
//
|
||||
// Notice: All APIs in this package are EXPERIMENTAL and may be changed
|
||||
// or removed in a later release.
|
||||
package weight
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
import "google.golang.org/grpc/resolver"
|
||||
|
||||
// attributeKey is the type used as the key to store EndpointInfo in the
|
||||
// Attributes field of resolver.Endpoint.
|
||||
type attributeKey struct{}
|
||||
|
||||
// EndpointInfo will be stored in the Attributes field of Endpoints in order to
|
||||
// use the ringhash balancer.
|
||||
// EndpointInfo will be stored in the Attributes field of Endpoints.
|
||||
type EndpointInfo struct {
|
||||
Weight uint32
|
||||
}
|
||||
@@ -43,22 +43,16 @@ func (a EndpointInfo) Equal(o any) bool {
|
||||
return ok && oa.Weight == a.Weight
|
||||
}
|
||||
|
||||
// Set returns a copy of endpoint in which the Attributes field is updated with
|
||||
// EndpointInfo.
|
||||
// Set returns a copy of endpoint in which the Attributes field is
|
||||
// updated with EndpointInfo.
|
||||
func Set(endpoint resolver.Endpoint, epInfo EndpointInfo) resolver.Endpoint {
|
||||
endpoint.Attributes = endpoint.Attributes.WithValue(attributeKey{}, epInfo)
|
||||
return endpoint
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of EndpointInfo.
|
||||
// This method is intended for logging, testing, and debugging purposes only.
|
||||
// Do not rely on the output format, as it is not guaranteed to remain stable.
|
||||
func (a EndpointInfo) String() string {
|
||||
return fmt.Sprintf("Weight: %d", a.Weight)
|
||||
}
|
||||
|
||||
// FromEndpoint returns the EndpointInfo stored in the Attributes field of an
|
||||
// endpoint. It returns an empty EndpointInfo if attribute is not found.
|
||||
// FromEndpoint returns the EndpointInfo stored in the Attributes
|
||||
// field of an endpoint. It returns an empty EndpointInfo if attribute
|
||||
// is not found.
|
||||
func FromEndpoint(endpoint resolver.Endpoint) EndpointInfo {
|
||||
v := endpoint.Attributes.Value(attributeKey{})
|
||||
ei, _ := v.(EndpointInfo)
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v5.27.1
|
||||
// source: grpc/health/v1/health.proto
|
||||
|
||||
|
||||
+85
-20
@@ -59,6 +59,15 @@ var (
|
||||
// unconditionally.
|
||||
XDSEndpointHashKeyBackwardCompat = boolFromEnv("GRPC_XDS_ENDPOINT_HASH_KEY_BACKWARD_COMPAT", false)
|
||||
|
||||
// LabelServerGoroutines controls setting [runtime/pprof.Labels] on the
|
||||
// goroutines spawned by [grpc.Server] type.
|
||||
// For now, this is limited to the goroutines spawned to handle incoming
|
||||
// requests on the server.
|
||||
// Set "GRPC_GO_SERVER_GOROUTINE_LABELS" to "grpc.method=true" to
|
||||
// enable this grpc.method label, or "all" to enable all valid labels.
|
||||
// This variable is a bit-field.
|
||||
LabelServerGoroutines = goroutineLabelsFromEnv("GRPC_GO_SERVER_GOROUTINE_LABELS", 0)
|
||||
|
||||
// RingHashSetRequestHashKey is set if the ring hash balancer can get the
|
||||
// request hash header by setting the "requestHashHeader" field, according
|
||||
// to gRFC A76. It can be disabled by setting the environment variable
|
||||
@@ -78,12 +87,12 @@ var (
|
||||
EnableDefaultPortForProxyTarget = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_DEFAULT_PORT_FOR_PROXY_TARGET", true)
|
||||
|
||||
// CaseSensitiveBalancerRegistries is set if the balancer registry should be
|
||||
// case-sensitive. This is disabled by default, but can be enabled by setting
|
||||
// case-sensitive. This is enabled by default, but can be disabled by setting
|
||||
// the env variable "GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES"
|
||||
// to "true".
|
||||
// to "false".
|
||||
//
|
||||
// TODO: After 2 releases, we will enable the env var by default.
|
||||
CaseSensitiveBalancerRegistries = boolFromEnv("GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES", false)
|
||||
// This env varible will be removed in release v1.82.0.
|
||||
CaseSensitiveBalancerRegistries = boolFromEnv("GRPC_GO_EXPERIMENTAL_CASE_SENSITIVE_BALANCER_REGISTRIES", true)
|
||||
|
||||
// XDSAuthorityRewrite indicates whether xDS authority rewriting is enabled.
|
||||
// This feature is defined in gRFC A81 and is enabled by setting the
|
||||
@@ -104,22 +113,6 @@ var (
|
||||
// to "false".
|
||||
XDSRecoverPanicInResourceParsing = boolFromEnv("GRPC_GO_EXPERIMENTAL_XDS_RESOURCE_PANIC_RECOVERY", true)
|
||||
|
||||
// DisableStrictPathChecking indicates whether strict path checking is
|
||||
// disabled. This feature can be disabled by setting the environment
|
||||
// variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to "true".
|
||||
//
|
||||
// When strict path checking is enabled, gRPC will reject requests with
|
||||
// paths that do not conform to the gRPC over HTTP/2 specification found at
|
||||
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md.
|
||||
//
|
||||
// When disabled, gRPC will allow paths that do not contain a leading slash.
|
||||
// Enabling strict path checking is recommended for security reasons, as it
|
||||
// prevents potential path traversal vulnerabilities.
|
||||
//
|
||||
// A future release will remove this environment variable, enabling strict
|
||||
// path checking behavior unconditionally.
|
||||
DisableStrictPathChecking = boolFromEnv("GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING", false)
|
||||
|
||||
// EnablePriorityLBChildPolicyCache controls whether the priority balancer
|
||||
// should cache child balancers that are removed from the LB policy config,
|
||||
// for a period of 15 minutes. This is disabled by default, but can be
|
||||
@@ -127,6 +120,18 @@ var (
|
||||
// GRPC_EXPERIMENTAL_ENABLE_PRIORITY_LB_CHILD_POLICY_CACHE to true.
|
||||
EnablePriorityLBChildPolicyCache = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_PRIORITY_LB_CHILD_POLICY_CACHE", false)
|
||||
|
||||
// Enable8KBDefaultHeaderListSize indicates that default maximum header list
|
||||
// size is restricted to 8KB. This is disabled by default, but can be enabled
|
||||
// by setting the environment variable
|
||||
// "GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE" to "true".
|
||||
// When disabled, the default maximum header list size of 16MB is used.
|
||||
//
|
||||
// When enabled, RPCs with a total size of headers exceeding 8KB will fail
|
||||
// unless explicitly configured otherwise by the user.
|
||||
//
|
||||
// TODO: In release v1.82.0, env var will be enabled by default.
|
||||
Enable8KBDefaultHeaderListSize = boolFromEnv("GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE", false)
|
||||
|
||||
// EnableHTTPFramerReadBufferPooling enables the use of the
|
||||
// readyreader.Reader interface to perform non-memory-pinning reads,
|
||||
// provided the underlying net.Conn supports it. This reduces memory usage
|
||||
@@ -136,6 +141,17 @@ var (
|
||||
// feature if unforeseen issues arise, and it will be removed in a future
|
||||
// release.
|
||||
EnableHTTPFramerReadBufferPooling = boolFromEnv("GRPC_GO_EXPERIMENTAL_HTTP_FRAMER_READ_BUFFER_POOLING", true)
|
||||
|
||||
// ControlBufferThrottleLimit is the maximum number of control frames that can
|
||||
// be queued in the control buffer before throttling is applied. The value
|
||||
// must be between 1 and 10,000, and is set to 100 by default.
|
||||
//
|
||||
// This environment variable serves as an escape hatch to increase the
|
||||
// throttling limit if unforeseen issues arise, and it will be removed in a
|
||||
// future release.
|
||||
//
|
||||
// TODO: Remove this env var once v1.83.0 is release.
|
||||
ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 100, 1, 10000)
|
||||
)
|
||||
|
||||
func boolFromEnv(envVar string, def bool) bool {
|
||||
@@ -160,3 +176,52 @@ func uint64FromEnv(envVar string, def, min, max uint64) uint64 {
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// GoroutineLabels is a bitfield indicating which goroutine labels are enabled.
|
||||
type GoroutineLabels uint16
|
||||
|
||||
func goroutineLabelsFromEnv(envVar string, def GoroutineLabels) GoroutineLabels {
|
||||
val := def
|
||||
v := os.Getenv(envVar)
|
||||
if strings.EqualFold(v, "all") {
|
||||
return AllGoroutineLabels
|
||||
} else if strings.EqualFold(v, "none") {
|
||||
return 0
|
||||
}
|
||||
for s := range strings.SplitSeq(v, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
pre, post, ok := strings.Cut(s, "=")
|
||||
if !ok {
|
||||
// no equals sign
|
||||
continue
|
||||
}
|
||||
post = strings.TrimSpace(post)
|
||||
pre = strings.TrimSpace(pre)
|
||||
bitDesignator := GoroutineLabels(0)
|
||||
switch {
|
||||
case strings.EqualFold(pre, "grpc.method"):
|
||||
bitDesignator = GoroutineLabelServerMethod
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if strings.EqualFold(post, "true") {
|
||||
val |= bitDesignator
|
||||
} else if strings.EqualFold(post, "false") {
|
||||
val &^= bitDesignator
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const (
|
||||
// GoroutineLabelServerMethod sets the grpc.method label on new
|
||||
// server-side gRPC streams.
|
||||
GoroutineLabelServerMethod GoroutineLabels = 1 << iota
|
||||
)
|
||||
|
||||
// AllGoroutineLabels is an or'd together bitfield of all valid GoroutineLabels
|
||||
// constant values (above).
|
||||
const AllGoroutineLabels = GoroutineLabelServerMethod
|
||||
|
||||
+10
@@ -89,4 +89,14 @@ var (
|
||||
// filtered and prefix-propagated to the LRS server. For more details, see:
|
||||
// https://github.com/grpc/proposal/blob/master/A85-lrs-custom-metrics-changes.md
|
||||
XDSORCAToLRSPropEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_ORCA_LRS_PROPAGATION", false)
|
||||
|
||||
// XDSClientExtProcEnabled indicates whether ExtProc filter is enabled on
|
||||
// the client side. For more details, see:
|
||||
// https://github.com/grpc/proposal/blob/master/A93-xds-ext-proc.md
|
||||
XDSClientExtProcEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_EXT_PROC_ON_CLIENT", false)
|
||||
|
||||
// GCPAuthenticationFilterEnabled enables the xDS GCP Authentication
|
||||
// filter. For more details, see:
|
||||
// https://github.com/grpc/proposal/blob/master/A83-xds-gcp-authn-filter.md
|
||||
GCPAuthenticationFilterEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_GCP_AUTHENTICATION_FILTER", false)
|
||||
)
|
||||
|
||||
-1
@@ -39,7 +39,6 @@ func div(d, r time.Duration) int64 {
|
||||
//
|
||||
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
|
||||
func EncodeDuration(t time.Duration) string {
|
||||
// TODO: This is simplistic and not bandwidth efficient. Improve it.
|
||||
if t <= 0 {
|
||||
return "0n"
|
||||
}
|
||||
|
||||
+17
-7
@@ -106,14 +106,24 @@ type ClientStream interface {
|
||||
|
||||
// ClientInterceptor is an interceptor for gRPC client streams.
|
||||
type ClientInterceptor interface {
|
||||
// NewStream produces a ClientStream for an RPC which may optionally use
|
||||
// the provided function to produce a stream for delegation. Note:
|
||||
// RPCInfo.Context should not be used (will be nil).
|
||||
// NewStream creates a ClientStream for an RPC.
|
||||
//
|
||||
// done is invoked when the RPC is finished using its connection, or could
|
||||
// not be assigned a connection. RPC operations may still occur on
|
||||
// ClientStream after done is called, since the interceptor is invoked by
|
||||
// application-layer operations. done must never be nil when called.
|
||||
// Implementations must delegate stream creation to the provided newStream
|
||||
// function. To intercept or override stream behavior, implementations
|
||||
// may wrap the ClientStream returned by the delegate.
|
||||
//
|
||||
// Note: RPCInfo.Context is currently unused and will be nil.
|
||||
//
|
||||
// The done function is invoked when the RPC has finished using its
|
||||
// underlying connection or if a connection could not be assigned. Because
|
||||
// interceptors operate at the application layer, RPC operations may
|
||||
// continue on the ClientStream even after done has been called. The
|
||||
// caller must ensure done is non-nil.
|
||||
//
|
||||
// To ensure RPC completion notifications propagate through the entire
|
||||
// interceptor chain, implementations must ensure that the done function
|
||||
// passed to the delegate newStream invokes the done function passed to
|
||||
// NewStream.
|
||||
NewStream(ctx context.Context, ri RPCInfo, done func(), newStream func(ctx context.Context, done func()) (ClientStream, error)) (ClientStream, error)
|
||||
// Close closes the interceptor. Once called, no new calls to NewStream are
|
||||
// accepted. Ongoing calls to NewStream are allowed to complete.
|
||||
|
||||
+46
-14
@@ -19,24 +19,56 @@
|
||||
// Package stats provides internal stats related functionality.
|
||||
package stats
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
)
|
||||
|
||||
// Labels are the labels for metrics.
|
||||
type Labels struct {
|
||||
// TelemetryLabels are the telemetry labels to record.
|
||||
TelemetryLabels map[string]string
|
||||
// LabelCallback is a function that is executed when telemetry
|
||||
// label keys are updated.
|
||||
type LabelCallback func(map[string]string)
|
||||
type telemetryLabelCallbackKey struct{}
|
||||
|
||||
// UpdateLabels executes registered telemetry callbacks with the update labels. Labels
|
||||
// are copied before being processed by any callbacks to ensure mutations are not
|
||||
// shared among derived contexts.
|
||||
//
|
||||
// It is the responsibility of the registrant to handle conflicts or label resets.
|
||||
func UpdateLabels(ctx context.Context, update map[string]string) {
|
||||
executeTelemetryLabelCallbacks(ctx, update)
|
||||
}
|
||||
|
||||
type labelsKey struct{}
|
||||
// RegisterTelemetryLabelCallback registers a callback function that is executed whenever
|
||||
// telemetry labels are updated.
|
||||
func RegisterTelemetryLabelCallback(ctx context.Context, callback LabelCallback) context.Context {
|
||||
if callback == nil {
|
||||
return ctx
|
||||
}
|
||||
|
||||
callbacks, ok := ctx.Value(telemetryLabelCallbackKey{}).([]LabelCallback)
|
||||
if !ok {
|
||||
return context.WithValue(ctx, telemetryLabelCallbackKey{}, []LabelCallback{callback})
|
||||
}
|
||||
return context.WithValue(ctx, telemetryLabelCallbackKey{}, append(append([]LabelCallback(nil), callbacks...), callback))
|
||||
|
||||
// GetLabels returns the Labels stored in the context, or nil if there is one.
|
||||
func GetLabels(ctx context.Context) *Labels {
|
||||
labels, _ := ctx.Value(labelsKey{}).(*Labels)
|
||||
return labels
|
||||
}
|
||||
|
||||
// SetLabels sets the Labels in the context.
|
||||
func SetLabels(ctx context.Context, labels *Labels) context.Context {
|
||||
// could also append
|
||||
return context.WithValue(ctx, labelsKey{}, labels)
|
||||
// executeTelemetryLabelCallback runs the registered callbacks in the order they were
|
||||
// registered on the context with the provided labels. If no callbacks are registered
|
||||
// it does nothing.
|
||||
//
|
||||
// To ensure callbacks do not mutate the state of the provided label map it is copied
|
||||
// before execution.
|
||||
func executeTelemetryLabelCallbacks(ctx context.Context, labels map[string]string) {
|
||||
callbacks, ok := ctx.Value(telemetryLabelCallbackKey{}).([]LabelCallback)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
labelsCopy := map[string]string{}
|
||||
maps.Copy(labelsCopy, labels)
|
||||
for _, callback := range callbacks {
|
||||
callback(labelsCopy)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
-1
@@ -19,6 +19,7 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
@@ -28,6 +29,12 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// nonGRPCDataMaxLen is the maximum length of nonGRPCDataBuf.
|
||||
//
|
||||
// NOTE: If changed this value, you MUST update the corresponding test in:
|
||||
// - /test/end2end_test.go:TestHTTPServerSendsNonGRPCHeaderSurfaceFurtherData
|
||||
const nonGRPCDataMaxLen = 1024
|
||||
|
||||
// ClientStream implements streaming functionality for a gRPC client.
|
||||
type ClientStream struct {
|
||||
Stream // Embed for common stream functionality.
|
||||
@@ -46,7 +53,11 @@ type ClientStream struct {
|
||||
// headerValid indicates whether a valid header was received. Only
|
||||
// meaningful after headerChan is closed (always call waitOnHeader() before
|
||||
// reading its value).
|
||||
headerValid bool
|
||||
headerValid bool
|
||||
|
||||
nonGRPCStatus *status.Status // the initial status from the non-gRPC response header, finalized with collected data before closing.
|
||||
nonGRPCDataBuf []byte // stores the data of a non-gRPC response.
|
||||
|
||||
noHeaders bool // set if the client never received headers (set only after the stream is done).
|
||||
headerChanClosed uint32 // set when headerChan is closed. Used to avoid closing headerChan multiple times.
|
||||
bytesReceived atomic.Bool // indicates whether any bytes have been received on this stream
|
||||
@@ -54,6 +65,29 @@ type ClientStream struct {
|
||||
statsHandler stats.Handler // nil for internal streams (e.g., health check, ORCA) where telemetry is not supported.
|
||||
}
|
||||
|
||||
func (s *ClientStream) startNonGRPCDataCollection(st *status.Status) {
|
||||
s.nonGRPCStatus = st
|
||||
s.nonGRPCDataBuf = make([]byte, 0, nonGRPCDataMaxLen)
|
||||
}
|
||||
|
||||
// finalizeNonGRPCStatus builds the terminal status by appending the collected
|
||||
// response body to the original non-gRPC status message.
|
||||
func (s *ClientStream) finalizeNonGRPCStatus() *status.Status {
|
||||
msg := fmt.Sprintf("%s\ndata: %q", s.nonGRPCStatus.Message(), s.nonGRPCDataBuf)
|
||||
return status.New(s.nonGRPCStatus.Code(), msg)
|
||||
}
|
||||
|
||||
// handleNonGRPCData collects non-gRPC body from the given data frame.
|
||||
// It returns non-nil value when the stream should be closed with it.
|
||||
func (s *ClientStream) handleNonGRPCData(f *parsedDataFrame) *status.Status {
|
||||
n := min(f.data.Len(), nonGRPCDataMaxLen-len(s.nonGRPCDataBuf))
|
||||
s.nonGRPCDataBuf = append(s.nonGRPCDataBuf, f.data.ReadOnlyData()[0:n]...)
|
||||
if len(s.nonGRPCDataBuf) >= nonGRPCDataMaxLen || f.StreamEnded() {
|
||||
return s.finalizeNonGRPCStatus()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
+97
-120
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/grpclog"
|
||||
"google.golang.org/grpc/mem"
|
||||
)
|
||||
@@ -96,61 +97,70 @@ func (il *itemList) isEmpty() bool {
|
||||
return il.head == nil
|
||||
}
|
||||
|
||||
// maxQueuedControlBufferItems is the maximum number of frames (other than
|
||||
// HEADERS and DATA) that we will buffer before preventing new reads from
|
||||
// occurring on the transport. These are control frames sent in response to
|
||||
// client requests, or frames that result in work being scheduled, such as
|
||||
// RST_STREAM due to bad headers or settings acks.
|
||||
var maxQueuedControlBufferItems = int(envconfig.ControlBufferThrottleLimit)
|
||||
|
||||
type cbItem interface {
|
||||
isThrottled() bool
|
||||
}
|
||||
|
||||
// throttledItem represents every item in the controlBuffer to which the overall
|
||||
// throttling limit applies, other than outgoing HEADERS and DATA frames.
|
||||
type throttledItem struct{}
|
||||
|
||||
func (throttledItem) isThrottled() bool { return true }
|
||||
|
||||
// The following defines various control items which could flow through
|
||||
// the control buffer of transport. They represent different aspects of
|
||||
// control tasks, e.g., flow control, settings, streaming resetting, etc.
|
||||
|
||||
// maxQueuedTransportResponseFrames is the most queued "transport response"
|
||||
// frames we will buffer before preventing new reads from occurring on the
|
||||
// transport. These are control frames sent in response to client requests,
|
||||
// such as RST_STREAM due to bad headers or settings acks.
|
||||
const maxQueuedTransportResponseFrames = 50
|
||||
|
||||
type cbItem interface {
|
||||
isTransportResponseFrame() bool
|
||||
}
|
||||
|
||||
// registerStream is used to register an incoming stream with loopy writer.
|
||||
type registerStream struct {
|
||||
throttledItem
|
||||
streamID uint32
|
||||
wq *writeQuota
|
||||
}
|
||||
|
||||
func (*registerStream) isTransportResponseFrame() bool { return false }
|
||||
|
||||
// headerFrame is also used to register stream on the client-side.
|
||||
type headerFrame struct {
|
||||
type clientHeaders struct {
|
||||
streamID uint32
|
||||
hf []hpack.HeaderField
|
||||
endStream bool // Valid on server side.
|
||||
initStream func(uint32) error // Used only on the client side.
|
||||
initStream func(uint32) error
|
||||
onWrite func()
|
||||
wq *writeQuota // write quota for the stream created.
|
||||
cleanup *cleanupStream // Valid on the server side.
|
||||
onOrphaned func(error) // Valid on client-side
|
||||
wq *writeQuota
|
||||
onOrphaned func(error)
|
||||
}
|
||||
|
||||
func (h *headerFrame) isTransportResponseFrame() bool {
|
||||
return h.cleanup != nil && h.cleanup.rst // Results in a RST_STREAM
|
||||
func (*clientHeaders) isThrottled() bool { return false }
|
||||
|
||||
type serverHeaders struct {
|
||||
streamID uint32
|
||||
hf []hpack.HeaderField
|
||||
endStream bool
|
||||
onWrite func()
|
||||
cleanup *cleanupStream
|
||||
}
|
||||
|
||||
func (h *serverHeaders) isThrottled() bool { return false }
|
||||
|
||||
type cleanupStream struct {
|
||||
throttledItem
|
||||
streamID uint32
|
||||
rst bool
|
||||
rstCode http2.ErrCode
|
||||
onWrite func()
|
||||
}
|
||||
|
||||
func (c *cleanupStream) isTransportResponseFrame() bool { return c.rst } // Results in a RST_STREAM
|
||||
|
||||
type earlyAbortStream struct {
|
||||
throttledItem
|
||||
streamID uint32
|
||||
rst bool
|
||||
hf []hpack.HeaderField // Pre-built header fields
|
||||
}
|
||||
|
||||
func (*earlyAbortStream) isTransportResponseFrame() bool { return false }
|
||||
|
||||
type dataFrame struct {
|
||||
streamID uint32
|
||||
endStream bool
|
||||
@@ -162,70 +172,60 @@ type dataFrame struct {
|
||||
onEachWrite func()
|
||||
}
|
||||
|
||||
func (*dataFrame) isTransportResponseFrame() bool { return false }
|
||||
func (*dataFrame) isThrottled() bool { return false }
|
||||
|
||||
type incomingWindowUpdate struct {
|
||||
throttledItem
|
||||
streamID uint32
|
||||
increment uint32
|
||||
}
|
||||
|
||||
func (*incomingWindowUpdate) isTransportResponseFrame() bool { return false }
|
||||
|
||||
type outgoingWindowUpdate struct {
|
||||
throttledItem
|
||||
streamID uint32
|
||||
increment uint32
|
||||
}
|
||||
|
||||
func (*outgoingWindowUpdate) isTransportResponseFrame() bool {
|
||||
return false // window updates are throttled by thresholds
|
||||
}
|
||||
|
||||
type incomingSettings struct {
|
||||
throttledItem
|
||||
ss []http2.Setting
|
||||
}
|
||||
|
||||
func (*incomingSettings) isTransportResponseFrame() bool { return true } // Results in a settings ACK
|
||||
|
||||
type outgoingSettings struct {
|
||||
throttledItem
|
||||
ss []http2.Setting
|
||||
}
|
||||
|
||||
func (*outgoingSettings) isTransportResponseFrame() bool { return false }
|
||||
|
||||
type incomingGoAway struct {
|
||||
throttledItem
|
||||
}
|
||||
|
||||
func (*incomingGoAway) isTransportResponseFrame() bool { return false }
|
||||
|
||||
type goAway struct {
|
||||
throttledItem
|
||||
code http2.ErrCode
|
||||
debugData []byte
|
||||
headsUp bool
|
||||
closeConn error // if set, loopyWriter will exit with this error
|
||||
}
|
||||
|
||||
func (*goAway) isTransportResponseFrame() bool { return false }
|
||||
|
||||
type ping struct {
|
||||
throttledItem
|
||||
ack bool
|
||||
data [8]byte
|
||||
}
|
||||
|
||||
func (*ping) isTransportResponseFrame() bool { return true }
|
||||
|
||||
type outFlowControlSizeRequest struct {
|
||||
throttledItem
|
||||
resp chan uint32
|
||||
}
|
||||
|
||||
func (*outFlowControlSizeRequest) isTransportResponseFrame() bool { return false }
|
||||
|
||||
// closeConnection is an instruction to tell the loopy writer to flush the
|
||||
// framer and exit, which will cause the transport's connection to be closed
|
||||
// (by the client or server). The transport itself will close after the reader
|
||||
// encounters the EOF caused by the connection closure.
|
||||
type closeConnection struct{}
|
||||
|
||||
func (closeConnection) isTransportResponseFrame() bool { return false }
|
||||
type closeConnection struct {
|
||||
throttledItem
|
||||
}
|
||||
|
||||
type outStreamState int
|
||||
|
||||
@@ -379,9 +379,9 @@ func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) {
|
||||
c.consumerWaiting = false
|
||||
}
|
||||
c.list.enqueue(it)
|
||||
if it.isTransportResponseFrame() {
|
||||
if it.isThrottled() {
|
||||
c.transportResponseFrames++
|
||||
if c.transportResponseFrames == maxQueuedTransportResponseFrames {
|
||||
if c.transportResponseFrames == maxQueuedControlBufferItems {
|
||||
// We are adding the frame that puts us over the threshold; create
|
||||
// a throttling channel.
|
||||
ch := make(chan struct{})
|
||||
@@ -436,8 +436,8 @@ func (c *controlBuffer) getOnceLocked() (any, error) {
|
||||
return nil, nil
|
||||
}
|
||||
h := c.list.dequeue().(cbItem)
|
||||
if h.isTransportResponseFrame() {
|
||||
if c.transportResponseFrames == maxQueuedTransportResponseFrames {
|
||||
if h.isThrottled() {
|
||||
if c.transportResponseFrames == maxQueuedControlBufferItems {
|
||||
// We are removing the frame that put us over the
|
||||
// threshold; close and clear the throttling channel.
|
||||
ch := c.trfChan.Swap(nil)
|
||||
@@ -464,10 +464,8 @@ func (c *controlBuffer) finish() {
|
||||
// is still not aware of these yet.
|
||||
for head := c.list.dequeueAll(); head != nil; head = head.next {
|
||||
switch v := head.it.(type) {
|
||||
case *headerFrame:
|
||||
if v.onOrphaned != nil { // It will be nil on the server-side.
|
||||
v.onOrphaned(ErrConnClosing)
|
||||
}
|
||||
case *clientHeaders:
|
||||
v.onOrphaned(ErrConnClosing)
|
||||
case *dataFrame:
|
||||
if !v.processing {
|
||||
v.data.Free()
|
||||
@@ -680,42 +678,38 @@ func (l *loopyWriter) registerStreamHandler(h *registerStream) {
|
||||
l.estdStreams[h.streamID] = str
|
||||
}
|
||||
|
||||
func (l *loopyWriter) headerHandler(h *headerFrame) error {
|
||||
if l.side == serverSide {
|
||||
str, ok := l.estdStreams[h.streamID]
|
||||
if !ok {
|
||||
if l.logger.V(logLevel) {
|
||||
l.logger.Infof("Unrecognized streamID %d in loopyWriter", h.streamID)
|
||||
}
|
||||
return nil
|
||||
func (l *loopyWriter) serverHeaderHandler(hdr *serverHeaders) error {
|
||||
str, ok := l.estdStreams[hdr.streamID]
|
||||
if !ok {
|
||||
if l.logger.V(logLevel) {
|
||||
l.logger.Infof("Unrecognized streamID %d in loopyWriter", hdr.streamID)
|
||||
}
|
||||
// Case 1.A: Server is responding back with headers.
|
||||
if !h.endStream {
|
||||
return l.writeHeader(h.streamID, h.endStream, h.hf, h.onWrite)
|
||||
}
|
||||
// else: Case 1.B: Server wants to close stream.
|
||||
return nil
|
||||
}
|
||||
|
||||
if str.state != empty { // either active or waiting on stream quota.
|
||||
// add it str's list of items.
|
||||
str.itl.enqueue(h)
|
||||
return nil
|
||||
}
|
||||
if err := l.writeHeader(h.streamID, h.endStream, h.hf, h.onWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
return l.cleanupStreamHandler(h.cleanup)
|
||||
// Case 1: Server is responding back with headers.
|
||||
if !hdr.endStream {
|
||||
return l.writeHeader(hdr.streamID, hdr.endStream, hdr.hf, hdr.onWrite)
|
||||
}
|
||||
// Case 2: Client wants to originate stream.
|
||||
str := &outStream{
|
||||
id: h.streamID,
|
||||
state: empty,
|
||||
itl: &itemList{},
|
||||
wq: h.wq,
|
||||
|
||||
// Case 2: Server is closing the stream.
|
||||
if str.state != empty { // either active or waiting on stream quota.
|
||||
str.itl.enqueue(hdr)
|
||||
return nil
|
||||
}
|
||||
return l.originateStream(str, h)
|
||||
if err := l.writeHeader(hdr.streamID, hdr.endStream, hdr.hf, hdr.onWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
return l.cleanupStreamHandler(hdr.cleanup)
|
||||
}
|
||||
|
||||
func (l *loopyWriter) originateStream(str *outStream, hdr *headerFrame) error {
|
||||
func (l *loopyWriter) clientHeaderHandler(hdr *clientHeaders) error {
|
||||
str := &outStream{
|
||||
id: hdr.streamID,
|
||||
state: empty,
|
||||
itl: &itemList{},
|
||||
wq: hdr.wq,
|
||||
}
|
||||
// l.draining is set when handling GoAway. In which case, we want to avoid
|
||||
// creating new streams.
|
||||
if l.draining {
|
||||
@@ -726,7 +720,7 @@ func (l *loopyWriter) originateStream(str *outStream, hdr *headerFrame) error {
|
||||
if err := hdr.initStream(str.id); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.writeHeader(str.id, hdr.endStream, hdr.hf, hdr.onWrite); err != nil {
|
||||
if err := l.writeHeader(str.id, false, hdr.hf, hdr.onWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
l.estdStreams[str.id] = str
|
||||
@@ -882,8 +876,10 @@ func (l *loopyWriter) handle(i any) error {
|
||||
return l.incomingSettingsHandler(i)
|
||||
case *outgoingSettings:
|
||||
return l.outgoingSettingsHandler(i)
|
||||
case *headerFrame:
|
||||
return l.headerHandler(i)
|
||||
case *clientHeaders:
|
||||
return l.clientHeaderHandler(i)
|
||||
case *serverHeaders:
|
||||
return l.serverHeaderHandler(i)
|
||||
case *registerStream:
|
||||
l.registerStreamHandler(i)
|
||||
case *cleanupStream:
|
||||
@@ -956,39 +952,16 @@ func (l *loopyWriter) processData() (bool, error) {
|
||||
// from data is copied to h to make as big as the maximum possible HTTP2 frame
|
||||
// size.
|
||||
|
||||
if len(dataItem.h) == 0 && reader.Remaining() == 0 { // Empty data frame
|
||||
// Client sends out empty data frame with endStream = true
|
||||
if err := l.framer.writeData(dataItem.streamID, dataItem.endStream, nil); err != nil {
|
||||
return false, err
|
||||
}
|
||||
str.itl.dequeue() // remove the empty data item from stream
|
||||
reader.Close()
|
||||
if str.itl.isEmpty() {
|
||||
str.state = empty
|
||||
} else if trailer, ok := str.itl.peek().(*headerFrame); ok { // the next item is trailers.
|
||||
if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := l.cleanupStreamHandler(trailer.cleanup); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
l.activeStreams.enqueue(str)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isEmpty := len(dataItem.h) == 0 && reader.Remaining() == 0
|
||||
// Figure out the maximum size we can send
|
||||
maxSize := http2MaxFrameLen
|
||||
if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 { // stream-level flow control.
|
||||
strQuota := int(l.oiws) - str.bytesOutStanding
|
||||
if strQuota <= 0 && !isEmpty { // stream-level flow control.
|
||||
str.state = waitingOnStreamQuota
|
||||
return false, nil
|
||||
} else if maxSize > strQuota {
|
||||
maxSize = strQuota
|
||||
}
|
||||
if maxSize > int(l.sendQuota) { // connection-level flow control.
|
||||
maxSize = int(l.sendQuota)
|
||||
}
|
||||
maxSize = min(maxSize, max(strQuota, 0))
|
||||
maxSize = min(maxSize, int(l.sendQuota)) // connection-level flow control.
|
||||
// Compute how much of the header and data we can send within quota and max frame length
|
||||
hSize := min(maxSize, len(dataItem.h))
|
||||
dSize := min(maxSize-hSize, reader.Remaining())
|
||||
@@ -1039,19 +1012,23 @@ func (l *loopyWriter) processData() (bool, error) {
|
||||
reader.Close()
|
||||
str.itl.dequeue()
|
||||
}
|
||||
return false, l.updateStreamAfterWrite(str)
|
||||
}
|
||||
|
||||
func (l *loopyWriter) updateStreamAfterWrite(str *outStream) error {
|
||||
if str.itl.isEmpty() {
|
||||
str.state = empty
|
||||
} else if trailer, ok := str.itl.peek().(*headerFrame); ok { // The next item is trailers.
|
||||
} else if trailer, ok := str.itl.peek().(*serverHeaders); ok { // the next item is trailers.
|
||||
if err := l.writeHeader(trailer.streamID, trailer.endStream, trailer.hf, trailer.onWrite); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
if err := l.cleanupStreamHandler(trailer.cleanup); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
} else if int(l.oiws)-str.bytesOutStanding <= 0 { // Ran out of stream quota.
|
||||
str.state = waitingOnStreamQuota
|
||||
} else { // Otherwise add it back to the list of active streams.
|
||||
l.activeStreams.enqueue(str)
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-6
@@ -115,7 +115,6 @@ func (f *trInFlow) getSize() uint32 {
|
||||
return atomic.LoadUint32(&f.effectiveWindowSize)
|
||||
}
|
||||
|
||||
// TODO(mmukhi): Simplify this code.
|
||||
// inFlow deals with inbound flow control
|
||||
type inFlow struct {
|
||||
mu sync.Mutex
|
||||
@@ -174,14 +173,14 @@ func (f *inFlow) maybeAdjust(n uint32) uint32 {
|
||||
// onData is invoked when some data frame is received. It updates pendingData.
|
||||
func (f *inFlow) onData(n uint32) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
f.pendingData += n
|
||||
if f.pendingData+f.pendingUpdate > f.limit+f.delta {
|
||||
limit := f.limit
|
||||
rcvd := f.pendingData + f.pendingUpdate
|
||||
f.mu.Unlock()
|
||||
return fmt.Errorf("received %d-bytes data exceeding the limit %d bytes", rcvd, limit)
|
||||
}
|
||||
f.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -189,8 +188,9 @@ func (f *inFlow) onData(n uint32) error {
|
||||
// to be sent to the peer.
|
||||
func (f *inFlow) onRead(n uint32) uint32 {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if f.pendingData == 0 {
|
||||
f.mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
f.pendingData -= n
|
||||
@@ -205,9 +205,7 @@ func (f *inFlow) onRead(n uint32) uint32 {
|
||||
if f.pendingUpdate >= f.limit/4 {
|
||||
wu := f.pendingUpdate
|
||||
f.pendingUpdate = 0
|
||||
f.mu.Unlock()
|
||||
return wu
|
||||
}
|
||||
f.mu.Unlock()
|
||||
return 0
|
||||
}
|
||||
|
||||
+2
-2
@@ -479,8 +479,8 @@ func (ht *serverHandlerTransport) runStream() {
|
||||
|
||||
func (ht *serverHandlerTransport) incrMsgRecv() {}
|
||||
|
||||
func (ht *serverHandlerTransport) Drain(string) {
|
||||
panic("Drain() is not implemented")
|
||||
func (ht *serverHandlerTransport) Drain(s string) {
|
||||
ht.Close(errors.New(s))
|
||||
}
|
||||
|
||||
// mapRecvMsgError returns the non-nil err into the appropriate
|
||||
|
||||
+50
-8
@@ -39,6 +39,7 @@ import (
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/internal/channelz"
|
||||
icredentials "google.golang.org/grpc/internal/credentials"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/grpclog"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
"google.golang.org/grpc/internal/grpcutil"
|
||||
@@ -318,7 +319,13 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
|
||||
}
|
||||
writeBufSize := opts.WriteBufferSize
|
||||
readBufSize := opts.ReadBufferSize
|
||||
// The default header list size is moving from 16MB to 8KB. The 8KB limit
|
||||
// is only used if Enable8KBDefaultHeaderListSize is true; otherwise, the
|
||||
// old 16MB default is used. User-specified options always take precedence.
|
||||
maxHeaderListSize := defaultClientMaxHeaderListSize
|
||||
if envconfig.Enable8KBDefaultHeaderListSize {
|
||||
maxHeaderListSize = upcomingDefaultHeaderListSize
|
||||
}
|
||||
if opts.MaxHeaderListSize != nil {
|
||||
maxHeaderListSize = *opts.MaxHeaderListSize
|
||||
}
|
||||
@@ -799,9 +806,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler s
|
||||
close(s.headerChan)
|
||||
}
|
||||
}
|
||||
hdr := &headerFrame{
|
||||
hf: headerFields,
|
||||
endStream: false,
|
||||
hdr := &clientHeaders{
|
||||
hf: headerFields,
|
||||
initStream: func(uint32) error {
|
||||
t.mu.Lock()
|
||||
// TODO: handle transport closure in loopy instead and remove this
|
||||
@@ -879,8 +885,8 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler s
|
||||
return false
|
||||
}
|
||||
}
|
||||
if sz > int64(upcomingDefaultHeaderListSize) {
|
||||
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In a future release, this will be restricted to %d bytes.", sz, upcomingDefaultHeaderListSize, upcomingDefaultHeaderListSize)
|
||||
if !envconfig.Enable8KBDefaultHeaderListSize && sz > int64(upcomingDefaultHeaderListSize) {
|
||||
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In release v1.82.0, GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE will be enabled by default, enforcing this limit.", sz, upcomingDefaultHeaderListSize)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1224,10 +1230,30 @@ func (t *http2Client) handleData(f *parsedDataFrame) {
|
||||
t.closeStream(s, io.EOF, true, http2.ErrCodeFlowControl, status.New(codes.Internal, err.Error()), nil, false)
|
||||
return
|
||||
}
|
||||
|
||||
if s.nonGRPCStatus != nil {
|
||||
// The frame should be handled as a non-gRPC response body
|
||||
st := s.handleNonGRPCData(f)
|
||||
if st != nil {
|
||||
t.closeStream(s, st.Err(), true, http2.ErrCodeProtocol, st, nil, true)
|
||||
return
|
||||
}
|
||||
if w := s.fc.onRead(size); w > 0 {
|
||||
t.controlBuf.put(&outgoingWindowUpdate{
|
||||
streamID: s.id,
|
||||
increment: w,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
dataLen := f.data.Len()
|
||||
if f.Header().Flags.Has(http2.FlagDataPadded) {
|
||||
if w := s.fc.onRead(size - uint32(dataLen)); w > 0 {
|
||||
t.controlBuf.put(&outgoingWindowUpdate{s.id, w})
|
||||
t.controlBuf.put(&outgoingWindowUpdate{
|
||||
streamID: s.id,
|
||||
increment: w,
|
||||
})
|
||||
}
|
||||
}
|
||||
if dataLen > 0 {
|
||||
@@ -1468,6 +1494,17 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
|
||||
return
|
||||
}
|
||||
|
||||
// If we are collecting non-gRPC response data and receive a trailing
|
||||
// HEADERS frame with END_STREAM, finalize the buffered data and close
|
||||
// the stream.
|
||||
if s.nonGRPCStatus != nil {
|
||||
if endStream {
|
||||
st := s.finalizeNonGRPCStatus()
|
||||
t.closeStream(s, st.Err(), true, http2.ErrCodeProtocol, st, nil, true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
// If a gRPC Response-Headers has already been received, then it means
|
||||
// that the peer is speaking gRPC and we are in gRPC mode.
|
||||
@@ -1568,7 +1605,12 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
|
||||
}
|
||||
|
||||
se := status.New(grpcErrorCode, strings.Join(errs, "; "))
|
||||
t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, endStream)
|
||||
if endStream {
|
||||
t.closeStream(s, se.Err(), true, http2.ErrCodeProtocol, se, nil, true)
|
||||
return
|
||||
}
|
||||
|
||||
s.startNonGRPCDataCollection(se)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1839,7 +1881,7 @@ func (t *http2Client) getOutFlowWindow() int64 {
|
||||
resp := make(chan uint32, 1)
|
||||
timer := time.NewTimer(time.Second)
|
||||
defer timer.Stop()
|
||||
t.controlBuf.put(&outFlowControlSizeRequest{resp})
|
||||
t.controlBuf.put(&outFlowControlSizeRequest{resp: resp})
|
||||
select {
|
||||
case sz := <-resp:
|
||||
return int64(sz)
|
||||
|
||||
+20
-9
@@ -38,11 +38,13 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/grpclog"
|
||||
"google.golang.org/grpc/internal/grpcutil"
|
||||
"google.golang.org/grpc/internal/pretty"
|
||||
istatus "google.golang.org/grpc/internal/status"
|
||||
"google.golang.org/grpc/internal/syscall"
|
||||
transportinternal "google.golang.org/grpc/internal/transport/internal"
|
||||
"google.golang.org/grpc/mem"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -165,7 +167,13 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport,
|
||||
}
|
||||
writeBufSize := config.WriteBufferSize
|
||||
readBufSize := config.ReadBufferSize
|
||||
// The default header list size is moving from 16MB to 8KB. The 8KB limit
|
||||
// is only used if Enable8KBDefaultHeaderListSize is true; otherwise, the
|
||||
// old 16MB default is used. User-specified options always take precedence.
|
||||
maxHeaderListSize := defaultServerMaxHeaderListSize
|
||||
if envconfig.Enable8KBDefaultHeaderListSize {
|
||||
maxHeaderListSize = upcomingDefaultHeaderListSize
|
||||
}
|
||||
if config.MaxHeaderListSize != nil {
|
||||
maxHeaderListSize = *config.MaxHeaderListSize
|
||||
}
|
||||
@@ -802,7 +810,10 @@ func (t *http2Server) handleData(f *parsedDataFrame) {
|
||||
dataLen := f.data.Len()
|
||||
if f.Header().Flags.Has(http2.FlagDataPadded) {
|
||||
if w := s.fc.onRead(size - uint32(dataLen)); w > 0 {
|
||||
t.controlBuf.put(&outgoingWindowUpdate{s.id, w})
|
||||
t.controlBuf.put(&outgoingWindowUpdate{
|
||||
streamID: s.id,
|
||||
increment: w,
|
||||
})
|
||||
}
|
||||
}
|
||||
if dataLen > 0 {
|
||||
@@ -948,8 +959,8 @@ func (t *http2Server) checkForHeaderListSize(hf []hpack.HeaderField) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if sz > int64(upcomingDefaultHeaderListSize) {
|
||||
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In a future release, this will be restricted to %d bytes.", sz, upcomingDefaultHeaderListSize, upcomingDefaultHeaderListSize)
|
||||
if !envconfig.Enable8KBDefaultHeaderListSize && sz > int64(upcomingDefaultHeaderListSize) {
|
||||
t.logger.Warningf("Header list size to send (%d bytes) is larger than the upcoming default limit (%d bytes). In release v1.82.0, GRPC_GO_EXPERIMENTAL_ENABLE_8KB_DEFAULT_HEADER_LIST_SIZE will be enabled by default, enforcing this limit.", sz, upcomingDefaultHeaderListSize)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1039,7 +1050,7 @@ func (t *http2Server) writeHeaderLocked(s *ServerStream) error {
|
||||
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress})
|
||||
}
|
||||
headerFields = appendHeaderFieldsFromMD(headerFields, s.header)
|
||||
hf := &headerFrame{
|
||||
hf := &serverHeaders{
|
||||
streamID: s.id,
|
||||
hf: headerFields,
|
||||
endStream: false,
|
||||
@@ -1107,7 +1118,7 @@ func (t *http2Server) writeStatus(s *ServerStream, st *status.Status) error {
|
||||
|
||||
// Attach the trailer metadata.
|
||||
headerFields = appendHeaderFieldsFromMD(headerFields, s.trailer)
|
||||
trailingHeader := &headerFrame{
|
||||
trailingHeader := &serverHeaders{
|
||||
streamID: s.id,
|
||||
hf: headerFields,
|
||||
endStream: true,
|
||||
@@ -1317,7 +1328,7 @@ func (t *http2Server) deleteStream(s *ServerStream, eosReceived bool) {
|
||||
}
|
||||
|
||||
// finishStream closes the stream and puts the trailing headerFrame into controlbuf.
|
||||
func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrCode, hdr *headerFrame, eosReceived bool) {
|
||||
func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrCode, hdr *serverHeaders, 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.
|
||||
@@ -1441,14 +1452,14 @@ func (t *http2Server) socketMetrics() *channelz.EphemeralSocketMetrics {
|
||||
func (t *http2Server) incrMsgSent() {
|
||||
if channelz.IsOn() {
|
||||
t.channelz.SocketMetrics.MessagesSent.Add(1)
|
||||
t.channelz.SocketMetrics.LastMessageSentTimestamp.Add(1)
|
||||
t.channelz.SocketMetrics.LastMessageSentTimestamp.Store(transportinternal.TimeNowFunc())
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Server) incrMsgRecv() {
|
||||
if channelz.IsOn() {
|
||||
t.channelz.SocketMetrics.MessagesReceived.Add(1)
|
||||
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Add(1)
|
||||
t.channelz.SocketMetrics.LastMessageReceivedTimestamp.Store(transportinternal.TimeNowFunc())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1456,7 +1467,7 @@ func (t *http2Server) getOutFlowWindow() int64 {
|
||||
resp := make(chan uint32, 1)
|
||||
timer := time.NewTimer(time.Second)
|
||||
defer timer.Stop()
|
||||
t.controlBuf.put(&outFlowControlSizeRequest{resp})
|
||||
t.controlBuf.put(&outFlowControlSizeRequest{resp: resp})
|
||||
select {
|
||||
case sz := <-resp:
|
||||
return int64(sz)
|
||||
|
||||
Generated
Vendored
+7
-13
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2021 gRPC authors.
|
||||
* Copyright 2026 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,16 +16,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
package grpcutil
|
||||
// Package internal contains functionality internal to the transport package.
|
||||
package internal
|
||||
|
||||
import "regexp"
|
||||
|
||||
// FullMatchWithRegex returns whether the full text matches the regex provided.
|
||||
func FullMatchWithRegex(re *regexp.Regexp, text string) bool {
|
||||
if len(text) == 0 {
|
||||
return re.MatchString(text)
|
||||
}
|
||||
re.Longest()
|
||||
rem := re.FindString(text)
|
||||
return len(rem) == len(text)
|
||||
}
|
||||
// TimeNowFunc is a variable that can be set to override the default behavior of
|
||||
// getting the current time in nanoseconds. It is used in transport code to set
|
||||
// channelz timestamps, and is exposed here for testing purposes.
|
||||
var TimeNowFunc func() int64
|
||||
+5
@@ -35,6 +35,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/internal/channelz"
|
||||
"google.golang.org/grpc/internal/transport/internal"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/mem"
|
||||
"google.golang.org/grpc/metadata"
|
||||
@@ -46,6 +47,10 @@ import (
|
||||
|
||||
const logLevel = 2
|
||||
|
||||
func init() {
|
||||
internal.TimeNowFunc = func() int64 { return time.Now().UnixNano() }
|
||||
}
|
||||
|
||||
// recvMsg represents the received msg from the transport. All transport
|
||||
// protocol specific info has been removed.
|
||||
type recvMsg struct {
|
||||
|
||||
+36
-6
@@ -128,6 +128,16 @@ func NewGZIPDecompressor() Decompressor {
|
||||
}
|
||||
|
||||
func (d *gzipDecompressor) Do(r io.Reader) ([]byte, error) {
|
||||
return d.doWithMaxSize(r, math.MaxInt64)
|
||||
}
|
||||
|
||||
// doWithMaxSize behaves like Do but caps the size of the decompressed
|
||||
// payload at maxMessageSize+1 bytes. The Decompressor interface does not
|
||||
// allow extra parameters, so callers inside the package type-assert to
|
||||
// *gzipDecompressor to invoke this method directly. The +1 byte makes it
|
||||
// possible for the caller to detect that the limit was exceeded and
|
||||
// return ResourceExhausted instead of materializing an unbounded payload.
|
||||
func (d *gzipDecompressor) doWithMaxSize(r io.Reader, maxMessageSize int64) ([]byte, error) {
|
||||
var z *gzip.Reader
|
||||
switch maybeZ := d.pool.Get().(type) {
|
||||
case nil:
|
||||
@@ -148,7 +158,11 @@ func (d *gzipDecompressor) Do(r io.Reader) ([]byte, error) {
|
||||
z.Close()
|
||||
d.pool.Put(z)
|
||||
}()
|
||||
return io.ReadAll(z)
|
||||
var src io.Reader = z
|
||||
if maxMessageSize < math.MaxInt64 {
|
||||
src = io.LimitReader(z, maxMessageSize+1)
|
||||
}
|
||||
return io.ReadAll(src)
|
||||
}
|
||||
|
||||
func (d *gzipDecompressor) Type() string {
|
||||
@@ -830,15 +844,15 @@ func compress(in mem.BufferSlice, cp Compressor, compressor encoding.Compressor,
|
||||
if compressor != nil {
|
||||
z, err := compressor.Compress(w)
|
||||
if err != nil {
|
||||
return nil, 0, wrapErr(err)
|
||||
return nil, compressionNone, wrapErr(err)
|
||||
}
|
||||
for _, b := range in {
|
||||
if _, err := z.Write(b.ReadOnlyData()); err != nil {
|
||||
return nil, 0, wrapErr(err)
|
||||
return nil, compressionNone, wrapErr(err)
|
||||
}
|
||||
}
|
||||
if err := z.Close(); err != nil {
|
||||
return nil, 0, wrapErr(err)
|
||||
return nil, compressionNone, wrapErr(err)
|
||||
}
|
||||
} else {
|
||||
// This is obviously really inefficient since it fully materializes the data, but
|
||||
@@ -848,7 +862,7 @@ func compress(in mem.BufferSlice, cp Compressor, compressor encoding.Compressor,
|
||||
buf := in.MaterializeToBuffer(pool)
|
||||
defer buf.Free()
|
||||
if err := cp.Do(w, buf.ReadOnlyData()); err != nil {
|
||||
return nil, 0, wrapErr(err)
|
||||
return nil, compressionNone, wrapErr(err)
|
||||
}
|
||||
}
|
||||
return out, compressionMade, nil
|
||||
@@ -971,7 +985,20 @@ func recvAndDecompress(p *parser, s recvCompressor, dc Decompressor, maxReceiveM
|
||||
func decompress(compressor encoding.Compressor, d mem.BufferSlice, dc Decompressor, maxReceiveMessageSize int, pool mem.BufferPool) (mem.BufferSlice, error) {
|
||||
if dc != nil {
|
||||
r := d.Reader()
|
||||
uncompressed, err := dc.Do(r)
|
||||
// For the built-in gzip decompressor, bound the decompressed output
|
||||
// at maxReceiveMessageSize+1 so that a small but highly compressed
|
||||
// payload (a "zip bomb") cannot expand to gigabytes in memory before
|
||||
// the post-decompression size check below has a chance to fire. The
|
||||
// Decompressor interface does not accept an extra size parameter,
|
||||
// so we type-assert to invoke a size-aware helper. Third-party
|
||||
// Decompressor implementations keep the original Do behavior.
|
||||
var uncompressed []byte
|
||||
var err error
|
||||
if gd, ok := dc.(*gzipDecompressor); ok {
|
||||
uncompressed, err = gd.doWithMaxSize(r, int64(maxReceiveMessageSize))
|
||||
} else {
|
||||
uncompressed, err = dc.Do(r)
|
||||
}
|
||||
if err != nil {
|
||||
r.Close() // ensure buffers are reused
|
||||
return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message: %v", err)
|
||||
@@ -989,6 +1016,9 @@ func decompress(compressor encoding.Compressor, d mem.BufferSlice, dc Decompress
|
||||
r.Close() // ensure buffers are reused
|
||||
return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the message: %v", err)
|
||||
}
|
||||
if closer, ok := dcReader.(io.Closer); ok {
|
||||
defer closer.Close()
|
||||
}
|
||||
|
||||
// Read at most one byte more than the limit from the decompressor.
|
||||
// Unless the limit is MaxInt64, in which case, that's impossible, so
|
||||
|
||||
+27
-25
@@ -28,6 +28,7 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -150,8 +151,6 @@ type Server struct {
|
||||
|
||||
serverWorkerChannel chan func()
|
||||
serverWorkerChannelClose func()
|
||||
|
||||
strictPathCheckingLogEmitted atomic.Bool
|
||||
}
|
||||
|
||||
type serverOptions struct {
|
||||
@@ -250,10 +249,8 @@ func newJoinServerOption(opts ...ServerOption) ServerOption {
|
||||
// If this option is set to true every connection will release the buffer after
|
||||
// flushing the data on the wire.
|
||||
//
|
||||
// # Experimental
|
||||
//
|
||||
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
|
||||
// later release.
|
||||
// Deprecated: shared write buffer is enabled by default. SharedWriteBuffer
|
||||
// will be removed in a future release.
|
||||
func SharedWriteBuffer(val bool) ServerOption {
|
||||
return newFuncServerOption(func(o *serverOptions) {
|
||||
o.sharedWriteBuffer = val
|
||||
@@ -302,6 +299,14 @@ func InitialConnWindowSize(s int32) ServerOption {
|
||||
// window size to the value provided and disables dynamic flow control.
|
||||
// The lower bound for window size is 64K and any value smaller than that
|
||||
// will be ignored.
|
||||
//
|
||||
// Note that this also disables dynamic flow control for the connection,
|
||||
// falling back to a default static connection-level window of 64KB. To
|
||||
// use a larger connection-level window, you must also use the
|
||||
// [StaticConnWindowSize] ServerOption.
|
||||
//
|
||||
// Most users should not configure static flow control windows unless
|
||||
// operating in a memory-constrained environment.
|
||||
func StaticStreamWindowSize(s int32) ServerOption {
|
||||
return newFuncServerOption(func(o *serverOptions) {
|
||||
o.initialWindowSize = s
|
||||
@@ -313,6 +318,14 @@ func StaticStreamWindowSize(s int32) ServerOption {
|
||||
// window size to the value provided and disables dynamic flow control.
|
||||
// The lower bound for window size is 64K and any value smaller than that
|
||||
// will be ignored.
|
||||
//
|
||||
// Note that this also disables dynamic flow control for individual streams,
|
||||
// falling back to a default static connection-level window of 64KB. To
|
||||
// explicitly configure the stream-level window size, you must also use the
|
||||
// [StaticStreamWindowSize] ServerOption.
|
||||
//
|
||||
// Most users should not configure static flow control windows unless
|
||||
// operating in a memory-constrained environment.
|
||||
func StaticConnWindowSize(s int32) ServerOption {
|
||||
return newFuncServerOption(func(o *serverOptions) {
|
||||
o.initialConnWindowSize = s
|
||||
@@ -1787,6 +1800,12 @@ func (s *Server) handleMalformedMethodName(stream *transport.ServerStream, ti *t
|
||||
func (s *Server) handleStream(t transport.ServerTransport, stream *transport.ServerStream) {
|
||||
ctx := stream.Context()
|
||||
ctx = contextWithServer(ctx, s)
|
||||
if envconfig.LabelServerGoroutines&envconfig.GoroutineLabelServerMethod != 0 {
|
||||
// This method always runs in its own goroutine, so we can set a
|
||||
// goroutine label without needing to restore a previous context.
|
||||
ctx = pprof.WithLabels(ctx, pprof.Labels("grpc.method", stream.Method()))
|
||||
pprof.SetGoroutineLabels(ctx)
|
||||
}
|
||||
var ti *traceInfo
|
||||
if EnableTracing {
|
||||
tr := newTrace("grpc.Recv."+methodFamily(stream.Method()), stream.Method())
|
||||
@@ -1803,28 +1822,11 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Ser
|
||||
}
|
||||
}
|
||||
|
||||
sm := stream.Method()
|
||||
if sm == "" {
|
||||
sm, found := strings.CutPrefix(stream.Method(), "/")
|
||||
if !found {
|
||||
s.handleMalformedMethodName(stream, ti)
|
||||
return
|
||||
}
|
||||
if sm[0] != '/' {
|
||||
// TODO(easwars): Add a link to the CVE in the below log messages once
|
||||
// published.
|
||||
if envconfig.DisableStrictPathChecking {
|
||||
if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
|
||||
channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream received malformed method name %q. Allowing it because the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING is set to true, but this option will be removed in a future release.", sm)
|
||||
}
|
||||
} else {
|
||||
if old := s.strictPathCheckingLogEmitted.Swap(true); !old {
|
||||
channelz.Warningf(logger, s.channelz, "grpc: Server.handleStream rejected malformed method name %q. To temporarily allow such requests, set the environment variable GRPC_GO_EXPERIMENTAL_DISABLE_STRICT_PATH_CHECKING to true. Note that this is not recommended as it may allow requests to bypass security policies.", sm)
|
||||
}
|
||||
s.handleMalformedMethodName(stream, ti)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
sm = sm[1:]
|
||||
}
|
||||
pos := strings.LastIndex(sm, "/")
|
||||
if pos == -1 {
|
||||
s.handleMalformedMethodName(stream, ti)
|
||||
|
||||
+1
-1
@@ -19,4 +19,4 @@
|
||||
package grpc
|
||||
|
||||
// Version is the current grpc version.
|
||||
const Version = "1.81.1"
|
||||
const Version = "1.82.1"
|
||||
|
||||
+180
-50
@@ -28,6 +28,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/encoding/prototext"
|
||||
"google.golang.org/protobuf/internal/editiondefaults"
|
||||
"google.golang.org/protobuf/internal/filedesc"
|
||||
"google.golang.org/protobuf/internal/genid"
|
||||
"google.golang.org/protobuf/internal/strs"
|
||||
@@ -113,15 +114,16 @@ type Plugin struct {
|
||||
SupportedEditionsMinimum descriptorpb.Edition
|
||||
SupportedEditionsMaximum descriptorpb.Edition
|
||||
|
||||
fileReg *protoregistry.Files
|
||||
enumsByName map[protoreflect.FullName]*Enum
|
||||
messagesByName map[protoreflect.FullName]*Message
|
||||
annotateCode bool
|
||||
pathType pathType
|
||||
module string
|
||||
genFiles []*GeneratedFile
|
||||
opts Options
|
||||
err error
|
||||
featureSetDefaults *descriptorpb.FeatureSetDefaults
|
||||
fileReg *protoregistry.Files
|
||||
enumsByName map[protoreflect.FullName]*Enum
|
||||
messagesByName map[protoreflect.FullName]*Message
|
||||
annotateCode bool
|
||||
pathType pathType
|
||||
module string
|
||||
genFiles []*GeneratedFile
|
||||
opts Options
|
||||
err error
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -129,10 +131,11 @@ type Options struct {
|
||||
// generator parameter.
|
||||
//
|
||||
// Plugins for protoc can accept parameters from the command line,
|
||||
// passed in the --<lang>_out protoc, separated from the output
|
||||
// directory with a colon; e.g.,
|
||||
// passed in the --<lang>_opt protoc flag, in addition to the
|
||||
// (required) output directory in --<lang>_out; e.g.,
|
||||
//
|
||||
// --go_out=<param1>=<value1>,<param2>=<value2>:<output_directory>
|
||||
// --go_opt=<param1>=<value1>,<param2>=<value2>
|
||||
// --go_out=<output_directory>
|
||||
//
|
||||
// Parameters passed in this fashion as a comma-separated list of
|
||||
// key=value pairs will be passed to the ParamFunc.
|
||||
@@ -155,6 +158,13 @@ type Options struct {
|
||||
// for this package.
|
||||
ImportRewriteFunc func(GoImportPath) GoImportPath
|
||||
|
||||
// A custom FeatureSetDefaults to use instead of the compiled-in defaults.
|
||||
// If nil, the compiled-in go defaults are used.
|
||||
//
|
||||
// This is useful to override for plugins that need to use other languages' features. It can be
|
||||
// produced by using protoc or the compile_edition_defaults bazel rule.
|
||||
FeatureSetDefaults *descriptorpb.FeatureSetDefaults
|
||||
|
||||
// StripForEditionsDiff true means that the plugin will not emit certain
|
||||
// parts of the generated code in order to make it possible to compare a
|
||||
// proto2/proto3 file with its equivalent (according to proto spec)
|
||||
@@ -183,6 +193,15 @@ func (opts Options) New(req *pluginpb.CodeGeneratorRequest) (*Plugin, error) {
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
if opts.FeatureSetDefaults != nil {
|
||||
gen.featureSetDefaults = proto.Clone(opts.FeatureSetDefaults).(*descriptorpb.FeatureSetDefaults)
|
||||
} else {
|
||||
gen.featureSetDefaults = &descriptorpb.FeatureSetDefaults{}
|
||||
if err := proto.Unmarshal(editiondefaults.Defaults, gen.featureSetDefaults); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal editions defaults: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
packageNames := make(map[string]GoPackageName) // filename -> package name
|
||||
importPaths := make(map[string]GoImportPath) // filename -> import path
|
||||
apiLevel := make(map[string]gofeaturespb.GoFeatures_APILevel) // filename -> api level
|
||||
@@ -518,6 +537,38 @@ func (gen *Plugin) Response() *pluginpb.CodeGeneratorResponse {
|
||||
return resp
|
||||
}
|
||||
|
||||
func (gen *Plugin) defaultFeatures(fileDesc *descriptorpb.FileDescriptorProto) (*descriptorpb.FeatureSet, error) {
|
||||
defaults := gen.featureSetDefaults
|
||||
var edition descriptorpb.Edition
|
||||
switch fileDesc.GetSyntax() {
|
||||
case "editions":
|
||||
edition = fileDesc.GetEdition()
|
||||
case "proto3":
|
||||
edition = descriptorpb.Edition_EDITION_PROTO3
|
||||
default:
|
||||
edition = descriptorpb.Edition_EDITION_PROTO2
|
||||
}
|
||||
if edition < defaults.GetMinimumEdition() {
|
||||
return nil, fmt.Errorf("edition %v is lower than the minimum supported edition %v", edition, defaults.GetMinimumEdition())
|
||||
}
|
||||
if edition > defaults.GetMaximumEdition() && edition != descriptorpb.Edition_EDITION_UNSTABLE {
|
||||
return nil, fmt.Errorf("edition %v is greater than the maximum supported edition %v", edition, defaults.GetMaximumEdition())
|
||||
}
|
||||
var match *descriptorpb.FeatureSetDefaults_FeatureSetEditionDefault
|
||||
for _, d := range gen.featureSetDefaults.GetDefaults() {
|
||||
if d.GetEdition().Number() > edition.Number() {
|
||||
break
|
||||
}
|
||||
match = d
|
||||
}
|
||||
if match == nil {
|
||||
return nil, fmt.Errorf("edition %v does not have a default FeatureSet supplied", edition)
|
||||
}
|
||||
result := proto.Clone(match.GetOverridableFeatures()).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(result, match.GetFixedFeatures())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// A File describes a .proto source file.
|
||||
type File struct {
|
||||
Desc protoreflect.FileDescriptor
|
||||
@@ -532,6 +583,8 @@ type File struct {
|
||||
Extensions []*Extension // top-level extension declarations
|
||||
Services []*Service // top-level service declarations
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for this file
|
||||
|
||||
Generate bool // true if we should generate code for this file
|
||||
|
||||
// GeneratedFilenamePrefix is used to construct filenames for generated
|
||||
@@ -559,12 +612,18 @@ func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPac
|
||||
if apiLevel != gofeaturespb.GoFeatures_API_LEVEL_UNSPECIFIED {
|
||||
defaultAPILevel = apiLevel
|
||||
}
|
||||
features, err := gen.defaultFeatures(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proto.Merge(features, p.GetOptions().GetFeatures())
|
||||
f := &File{
|
||||
Desc: desc,
|
||||
Proto: p,
|
||||
GoPackageName: packageName,
|
||||
GoImportPath: importPath,
|
||||
location: Location{SourceFile: desc.Path()},
|
||||
Desc: desc,
|
||||
Proto: p,
|
||||
GoPackageName: packageName,
|
||||
GoImportPath: importPath,
|
||||
ResolvedFeatures: features,
|
||||
location: Location{SourceFile: desc.Path()},
|
||||
|
||||
APILevel: fileAPILevel(desc, defaultAPILevel),
|
||||
}
|
||||
@@ -595,7 +654,7 @@ func newFile(gen *Plugin, p *descriptorpb.FileDescriptorProto, packageName GoPac
|
||||
f.Messages = append(f.Messages, newMessage(gen, f, nil, mds.Get(i)))
|
||||
}
|
||||
for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ {
|
||||
f.Extensions = append(f.Extensions, newField(gen, f, nil, xds.Get(i)))
|
||||
f.Extensions = append(f.Extensions, newField(gen, f, nil, nil, xds.Get(i)))
|
||||
}
|
||||
for i, sds := 0, desc.Services(); i < sds.Len(); i++ {
|
||||
f.Services = append(f.Services, newService(gen, f, sds.Get(i)))
|
||||
@@ -639,20 +698,30 @@ type Enum struct {
|
||||
|
||||
Location Location // location of this enum
|
||||
Comments CommentSet // comments associated with this enum
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for this enum
|
||||
}
|
||||
|
||||
func newEnum(gen *Plugin, f *File, parent *Message, desc protoreflect.EnumDescriptor) *Enum {
|
||||
var loc Location
|
||||
var features *descriptorpb.FeatureSet
|
||||
if parent != nil {
|
||||
loc = parent.Location.appendPath(genid.DescriptorProto_EnumType_field_number, desc.Index())
|
||||
features = parent.ResolvedFeatures
|
||||
} else {
|
||||
loc = f.location.appendPath(genid.FileDescriptorProto_EnumType_field_number, desc.Index())
|
||||
features = f.ResolvedFeatures
|
||||
}
|
||||
if desc.Options().(*descriptorpb.EnumOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.EnumOptions).GetFeatures())
|
||||
}
|
||||
enum := &Enum{
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
}
|
||||
gen.enumsByName[desc.FullName()] = enum
|
||||
for i, vds := 0, enum.Desc.Values(); i < vds.Len(); i++ {
|
||||
@@ -677,6 +746,8 @@ type EnumValue struct {
|
||||
|
||||
Location Location // location of this enum value
|
||||
Comments CommentSet // comments associated with this enum value
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for this enum value
|
||||
}
|
||||
|
||||
func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc protoreflect.EnumValueDescriptor) *EnumValue {
|
||||
@@ -718,12 +789,18 @@ func newEnumValue(gen *Plugin, f *File, message *Message, enum *Enum, desc proto
|
||||
name = parentIdent.GoName + "_" + strs.TrimEnumPrefix(string(desc.Name()), prefix)
|
||||
}
|
||||
}
|
||||
features := enum.ResolvedFeatures
|
||||
if desc.Options().(*descriptorpb.EnumValueOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.EnumValueOptions).GetFeatures())
|
||||
}
|
||||
ev := &EnumValue{
|
||||
Desc: desc,
|
||||
GoIdent: f.GoImportPath.Ident(name),
|
||||
Parent: enum,
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Desc: desc,
|
||||
GoIdent: f.GoImportPath.Ident(name),
|
||||
Parent: enum,
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
}
|
||||
if prefixedName != "" {
|
||||
ev.PrefixedAlias = f.GoImportPath.Ident(prefixedName)
|
||||
@@ -747,16 +824,21 @@ type Message struct {
|
||||
Location Location // location of this message
|
||||
Comments CommentSet // comments associated with this message
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for this message
|
||||
|
||||
// APILevel specifies which API to generate. One of OPEN, HYBRID or OPAQUE.
|
||||
APILevel gofeaturespb.GoFeatures_APILevel
|
||||
}
|
||||
|
||||
func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.MessageDescriptor) *Message {
|
||||
var loc Location
|
||||
var features *descriptorpb.FeatureSet
|
||||
if parent != nil {
|
||||
loc = parent.Location.appendPath(genid.DescriptorProto_NestedType_field_number, desc.Index())
|
||||
features = parent.ResolvedFeatures
|
||||
} else {
|
||||
loc = f.location.appendPath(genid.FileDescriptorProto_MessageType_field_number, desc.Index())
|
||||
features = f.ResolvedFeatures
|
||||
}
|
||||
|
||||
def := f.APILevel
|
||||
@@ -765,11 +847,16 @@ func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.Message
|
||||
def = parent.APILevel
|
||||
}
|
||||
|
||||
if desc.Options().(*descriptorpb.MessageOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.MessageOptions).GetFeatures())
|
||||
}
|
||||
message := &Message{
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Desc: desc,
|
||||
GoIdent: newGoIdent(f, desc),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
|
||||
APILevel: messageAPILevel(desc, def),
|
||||
}
|
||||
@@ -780,14 +867,18 @@ func newMessage(gen *Plugin, f *File, parent *Message, desc protoreflect.Message
|
||||
for i, mds := 0, desc.Messages(); i < mds.Len(); i++ {
|
||||
message.Messages = append(message.Messages, newMessage(gen, f, message, mds.Get(i)))
|
||||
}
|
||||
for i, fds := 0, desc.Fields(); i < fds.Len(); i++ {
|
||||
message.Fields = append(message.Fields, newField(gen, f, message, fds.Get(i)))
|
||||
}
|
||||
for i, ods := 0, desc.Oneofs(); i < ods.Len(); i++ {
|
||||
message.Oneofs = append(message.Oneofs, newOneof(gen, f, message, ods.Get(i)))
|
||||
}
|
||||
for i, fds := 0, desc.Fields(); i < fds.Len(); i++ {
|
||||
var oneof *Oneof
|
||||
if fds.Get(i).ContainingOneof() != nil {
|
||||
oneof = message.Oneofs[fds.Get(i).ContainingOneof().Index()]
|
||||
}
|
||||
message.Fields = append(message.Fields, newField(gen, f, message, oneof, fds.Get(i)))
|
||||
}
|
||||
for i, xds := 0, desc.Extensions(); i < xds.Len(); i++ {
|
||||
message.Extensions = append(message.Extensions, newField(gen, f, message, xds.Get(i)))
|
||||
message.Extensions = append(message.Extensions, newField(gen, f, message, nil, xds.Get(i)))
|
||||
}
|
||||
|
||||
// Resolve local references between fields and oneofs.
|
||||
@@ -918,6 +1009,8 @@ type Field struct {
|
||||
Location Location // location of this field
|
||||
Comments CommentSet // comments associated with this field
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for the field
|
||||
|
||||
// camelCase is the same as GoName, but without the name
|
||||
// mangling. This is used in builders, where only the single
|
||||
// name "Build" needs to be mangled.
|
||||
@@ -931,21 +1024,33 @@ type Field struct {
|
||||
hasConflictHybrid bool
|
||||
}
|
||||
|
||||
func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDescriptor) *Field {
|
||||
func newField(gen *Plugin, f *File, message *Message, oneof *Oneof, desc protoreflect.FieldDescriptor) *Field {
|
||||
var loc Location
|
||||
var features *descriptorpb.FeatureSet
|
||||
switch {
|
||||
case desc.IsExtension() && message == nil:
|
||||
loc = f.location.appendPath(genid.FileDescriptorProto_Extension_field_number, desc.Index())
|
||||
features = f.ResolvedFeatures
|
||||
case desc.IsExtension() && message != nil:
|
||||
loc = message.Location.appendPath(genid.DescriptorProto_Extension_field_number, desc.Index())
|
||||
features = message.ResolvedFeatures
|
||||
default:
|
||||
loc = message.Location.appendPath(genid.DescriptorProto_Field_field_number, desc.Index())
|
||||
if oneof != nil {
|
||||
features = oneof.ResolvedFeatures
|
||||
} else {
|
||||
features = message.ResolvedFeatures
|
||||
}
|
||||
}
|
||||
camelCased := strs.GoCamelCase(string(desc.Name()))
|
||||
var parentPrefix string
|
||||
if message != nil {
|
||||
parentPrefix = message.GoIdent.GoName + "_"
|
||||
}
|
||||
if desc.Options().(*descriptorpb.FieldOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.FieldOptions).GetFeatures())
|
||||
}
|
||||
field := &Field{
|
||||
Desc: desc,
|
||||
GoName: camelCased,
|
||||
@@ -953,9 +1058,10 @@ func newField(gen *Plugin, f *File, message *Message, desc protoreflect.FieldDes
|
||||
GoImportPath: f.GoImportPath,
|
||||
GoName: parentPrefix + camelCased,
|
||||
},
|
||||
Parent: message,
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Parent: message,
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
}
|
||||
|
||||
opaqueNewFieldHook(desc, field)
|
||||
@@ -1011,6 +1117,8 @@ type Oneof struct {
|
||||
Location Location // location of this oneof
|
||||
Comments CommentSet // comments associated with this oneof
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for the oneof
|
||||
|
||||
// camelCase is the same as GoName, but without the name mangling.
|
||||
// This is used in builders, which never have their names mangled
|
||||
camelCase string
|
||||
@@ -1027,6 +1135,11 @@ func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDes
|
||||
loc := message.Location.appendPath(genid.DescriptorProto_OneofDecl_field_number, desc.Index())
|
||||
camelCased := strs.GoCamelCase(string(desc.Name()))
|
||||
parentPrefix := message.GoIdent.GoName + "_"
|
||||
features := message.ResolvedFeatures
|
||||
if desc.Options().(*descriptorpb.OneofOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.OneofOptions).GetFeatures())
|
||||
}
|
||||
oneof := &Oneof{
|
||||
Desc: desc,
|
||||
Parent: message,
|
||||
@@ -1035,8 +1148,9 @@ func newOneof(gen *Plugin, f *File, message *Message, desc protoreflect.OneofDes
|
||||
GoImportPath: f.GoImportPath,
|
||||
GoName: parentPrefix + camelCased,
|
||||
},
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
}
|
||||
|
||||
opaqueNewOneofHook(desc, oneof)
|
||||
@@ -1057,15 +1171,23 @@ type Service struct {
|
||||
|
||||
Location Location // location of this service
|
||||
Comments CommentSet // comments associated with this service
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for the service
|
||||
}
|
||||
|
||||
func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service {
|
||||
loc := f.location.appendPath(genid.FileDescriptorProto_Service_field_number, desc.Index())
|
||||
features := f.ResolvedFeatures
|
||||
if desc.Options().(*descriptorpb.ServiceOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.ServiceOptions).GetFeatures())
|
||||
}
|
||||
service := &Service{
|
||||
Desc: desc,
|
||||
GoName: strs.GoCamelCase(string(desc.Name())),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Desc: desc,
|
||||
GoName: strs.GoCamelCase(string(desc.Name())),
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
}
|
||||
for i, mds := 0, desc.Methods(); i < mds.Len(); i++ {
|
||||
service.Methods = append(service.Methods, newMethod(gen, f, service, mds.Get(i)))
|
||||
@@ -1086,16 +1208,24 @@ type Method struct {
|
||||
|
||||
Location Location // location of this method
|
||||
Comments CommentSet // comments associated with this method
|
||||
|
||||
ResolvedFeatures *descriptorpb.FeatureSet // resolved features for the service
|
||||
}
|
||||
|
||||
func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method {
|
||||
loc := service.Location.appendPath(genid.ServiceDescriptorProto_Method_field_number, desc.Index())
|
||||
features := service.ResolvedFeatures
|
||||
if desc.Options().(*descriptorpb.MethodOptions).GetFeatures() != nil {
|
||||
features = proto.Clone(features).(*descriptorpb.FeatureSet)
|
||||
proto.Merge(features, desc.Options().(*descriptorpb.MethodOptions).GetFeatures())
|
||||
}
|
||||
method := &Method{
|
||||
Desc: desc,
|
||||
GoName: strs.GoCamelCase(string(desc.Name())),
|
||||
Parent: service,
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
Desc: desc,
|
||||
GoName: strs.GoCamelCase(string(desc.Name())),
|
||||
Parent: service,
|
||||
Location: loc,
|
||||
Comments: makeCommentSet(gen, f.Desc.SourceLocations().ByDescriptor(desc)),
|
||||
ResolvedFeatures: features,
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
+12
@@ -365,6 +365,10 @@ func unmarshalInt(tok json.Token, bitSize int) (protoreflect.Value, bool) {
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, false
|
||||
}
|
||||
// Ensure there is no non-number content in this string.
|
||||
if next, err := dec.Read(); err != nil || next.Kind() != json.EOF {
|
||||
return protoreflect.Value{}, false
|
||||
}
|
||||
return getInt(tok, bitSize)
|
||||
}
|
||||
return protoreflect.Value{}, false
|
||||
@@ -397,6 +401,10 @@ func unmarshalUint(tok json.Token, bitSize int) (protoreflect.Value, bool) {
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, false
|
||||
}
|
||||
// Ensure there is no non-number content in this string.
|
||||
if next, err := dec.Read(); err != nil || next.Kind() != json.EOF {
|
||||
return protoreflect.Value{}, false
|
||||
}
|
||||
return getUint(tok, bitSize)
|
||||
}
|
||||
return protoreflect.Value{}, false
|
||||
@@ -447,6 +455,10 @@ func unmarshalFloat(tok json.Token, bitSize int) (protoreflect.Value, bool) {
|
||||
if err != nil {
|
||||
return protoreflect.Value{}, false
|
||||
}
|
||||
// Ensure there is no non-number content in this string.
|
||||
if next, err := dec.Read(); err != nil || next.Kind() != json.EOF {
|
||||
return protoreflect.Value{}, false
|
||||
}
|
||||
return getFloat(tok, bitSize)
|
||||
}
|
||||
return protoreflect.Value{}, false
|
||||
|
||||
+4
-1
@@ -52,7 +52,10 @@ func wellKnownTypeMarshaler(name protoreflect.FullName) marshalFunc {
|
||||
case genid.FieldMask_message_name:
|
||||
return encoder.marshalFieldMask
|
||||
case genid.Empty_message_name:
|
||||
return encoder.marshalEmpty
|
||||
// The spec explicitly specifies that the Empty message
|
||||
// is not considered to have any special JSON mapping:
|
||||
// https://protobuf.dev/programming-guides/json/#any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+18
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
"google.golang.org/protobuf/internal/encoding/messageset"
|
||||
"google.golang.org/protobuf/internal/encoding/text"
|
||||
"google.golang.org/protobuf/internal/errors"
|
||||
@@ -49,12 +50,19 @@ type UnmarshalOptions struct {
|
||||
protoregistry.MessageTypeResolver
|
||||
protoregistry.ExtensionTypeResolver
|
||||
}
|
||||
|
||||
// RecursionLimit limits how deeply messages may be nested.
|
||||
// If zero, a default limit is applied.
|
||||
RecursionLimit int
|
||||
}
|
||||
|
||||
// Unmarshal reads the given []byte and populates the given [proto.Message]
|
||||
// using options in the UnmarshalOptions object.
|
||||
// The provided message must be mutable (e.g., a non-nil pointer to a message).
|
||||
func (o UnmarshalOptions) Unmarshal(b []byte, m proto.Message) error {
|
||||
if o.RecursionLimit == 0 {
|
||||
o.RecursionLimit = protowire.DefaultRecursionLimit
|
||||
}
|
||||
return o.unmarshal(b, m)
|
||||
}
|
||||
|
||||
@@ -102,8 +110,14 @@ func (d decoder) syntaxError(pos int, f string, x ...any) error {
|
||||
return errors.New(head+f, x...)
|
||||
}
|
||||
|
||||
var errRecursionDepth = errors.New("exceeded maximum recursion depth")
|
||||
|
||||
// unmarshalMessage unmarshals into the given protoreflect.Message.
|
||||
func (d decoder) unmarshalMessage(m protoreflect.Message, checkDelims bool) error {
|
||||
if d.opts.RecursionLimit--; d.opts.RecursionLimit < 0 {
|
||||
return errRecursionDepth
|
||||
}
|
||||
|
||||
messageDesc := m.Descriptor()
|
||||
if !flags.ProtoLegacy && messageset.IsMessageSet(messageDesc) {
|
||||
return errors.New("no support for proto1 MessageSets")
|
||||
@@ -437,6 +451,10 @@ func (d decoder) unmarshalList(fd protoreflect.FieldDescriptor, list protoreflec
|
||||
// unmarshalMap unmarshals into given protoreflect.Map. A map value is a
|
||||
// textproto message containing {key: <kvalue>, value: <mvalue>}.
|
||||
func (d decoder) unmarshalMap(fd protoreflect.FieldDescriptor, mmap protoreflect.Map) error {
|
||||
if d.opts.RecursionLimit--; d.opts.RecursionLimit < 0 {
|
||||
return errRecursionDepth
|
||||
}
|
||||
|
||||
// Determine ahead whether map entry is a scalar type or a message type in
|
||||
// order to call the appropriate unmarshalMapValue func inside
|
||||
// unmarshalMapEntry.
|
||||
|
||||
+135
-140
@@ -83,12 +83,13 @@ func formatListOpt(vs list, isRoot, allowMulti bool) string {
|
||||
case protoreflect.FileImports:
|
||||
for i := 0; i < vs.Len(); i++ {
|
||||
var rs records
|
||||
rv := reflect.ValueOf(vs.Get(i))
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Path"), "Path"},
|
||||
{rv.MethodByName("Package"), "Package"},
|
||||
{rv.MethodByName("IsPublic"), "IsPublic"},
|
||||
{rv.MethodByName("IsWeak"), "IsWeak"},
|
||||
fi := vs.Get(i)
|
||||
rv := reflect.ValueOf(fi)
|
||||
rs.Append(rv, []attrAndName{
|
||||
{fi.Path(), "Path"},
|
||||
{fi.Package(), "Package"},
|
||||
{fi.IsPublic, "IsPublic"},
|
||||
{fi.IsWeak, "IsWeak"},
|
||||
}...)
|
||||
ss = append(ss, "{"+rs.Join()+"}")
|
||||
}
|
||||
@@ -104,9 +105,9 @@ func formatListOpt(vs list, isRoot, allowMulti bool) string {
|
||||
}
|
||||
}
|
||||
|
||||
type methodAndName struct {
|
||||
method reflect.Value
|
||||
name string
|
||||
type attrAndName struct {
|
||||
attr any
|
||||
name string
|
||||
}
|
||||
|
||||
func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
|
||||
@@ -126,58 +127,58 @@ func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record fu
|
||||
start = rt.Name() + "{"
|
||||
}
|
||||
|
||||
_, isFile := t.(protoreflect.FileDescriptor)
|
||||
fd, isFile := t.(protoreflect.FileDescriptor)
|
||||
rs := records{
|
||||
allowMulti: allowMulti,
|
||||
record: record,
|
||||
}
|
||||
if t.IsPlaceholder() {
|
||||
if isFile {
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Path"), "Path"},
|
||||
{rv.MethodByName("Package"), "Package"},
|
||||
{rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{fd.Path(), "Path"},
|
||||
{fd.Package(), "Package"},
|
||||
{fd.IsPlaceholder(), "IsPlaceholder"},
|
||||
}...)
|
||||
} else {
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("FullName"), "FullName"},
|
||||
{rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{t.FullName(), "FullName"},
|
||||
{t.IsPlaceholder(), "IsPlaceholder"},
|
||||
}...)
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case isFile:
|
||||
rs.Append(rv, methodAndName{rv.MethodByName("Syntax"), "Syntax"})
|
||||
rs.Append(rv, attrAndName{fd.Syntax(), "Syntax"})
|
||||
case isRoot:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Syntax"), "Syntax"},
|
||||
{rv.MethodByName("FullName"), "FullName"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{t.Syntax(), "Syntax"},
|
||||
{t.FullName(), "FullName"},
|
||||
}...)
|
||||
default:
|
||||
rs.Append(rv, methodAndName{rv.MethodByName("Name"), "Name"})
|
||||
rs.Append(rv, attrAndName{t.Name(), "Name"})
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case protoreflect.FieldDescriptor:
|
||||
accessors := []methodAndName{
|
||||
{rv.MethodByName("Number"), "Number"},
|
||||
{rv.MethodByName("Cardinality"), "Cardinality"},
|
||||
{rv.MethodByName("Kind"), "Kind"},
|
||||
{rv.MethodByName("HasJSONName"), "HasJSONName"},
|
||||
{rv.MethodByName("JSONName"), "JSONName"},
|
||||
{rv.MethodByName("HasPresence"), "HasPresence"},
|
||||
{rv.MethodByName("IsExtension"), "IsExtension"},
|
||||
{rv.MethodByName("IsPacked"), "IsPacked"},
|
||||
{rv.MethodByName("IsWeak"), "IsWeak"},
|
||||
{rv.MethodByName("IsList"), "IsList"},
|
||||
{rv.MethodByName("IsMap"), "IsMap"},
|
||||
{rv.MethodByName("MapKey"), "MapKey"},
|
||||
{rv.MethodByName("MapValue"), "MapValue"},
|
||||
{rv.MethodByName("HasDefault"), "HasDefault"},
|
||||
{rv.MethodByName("Default"), "Default"},
|
||||
{rv.MethodByName("ContainingOneof"), "ContainingOneof"},
|
||||
{rv.MethodByName("ContainingMessage"), "ContainingMessage"},
|
||||
{rv.MethodByName("Message"), "Message"},
|
||||
{rv.MethodByName("Enum"), "Enum"},
|
||||
accessors := []attrAndName{
|
||||
{t.Number(), "Number"},
|
||||
{t.Cardinality(), "Cardinality"},
|
||||
{t.Kind(), "Kind"},
|
||||
{t.HasJSONName(), "HasJSONName"},
|
||||
{t.JSONName(), "JSONName"},
|
||||
{t.HasPresence(), "HasPresence"},
|
||||
{t.IsExtension(), "IsExtension"},
|
||||
{t.IsPacked(), "IsPacked"},
|
||||
{t.IsWeak(), "IsWeak"},
|
||||
{t.IsList(), "IsList"},
|
||||
{t.IsMap(), "IsMap"},
|
||||
{t.MapKey(), "MapKey"},
|
||||
{t.MapValue(), "MapValue"},
|
||||
{t.HasDefault(), "HasDefault"},
|
||||
{t.Default(), "Default"},
|
||||
{t.ContainingOneof(), "ContainingOneof"},
|
||||
{t.ContainingMessage(), "ContainingMessage"},
|
||||
{t.Message(), "Message"},
|
||||
{t.Enum(), "Enum"},
|
||||
}
|
||||
for _, s := range accessors {
|
||||
switch s.name {
|
||||
@@ -223,58 +224,54 @@ func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record fu
|
||||
}
|
||||
|
||||
case protoreflect.FileDescriptor:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Path"), "Path"},
|
||||
{rv.MethodByName("Package"), "Package"},
|
||||
{rv.MethodByName("Imports"), "Imports"},
|
||||
{rv.MethodByName("Messages"), "Messages"},
|
||||
{rv.MethodByName("Enums"), "Enums"},
|
||||
{rv.MethodByName("Extensions"), "Extensions"},
|
||||
{rv.MethodByName("Services"), "Services"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{t.Path(), "Path"},
|
||||
{t.Package(), "Package"},
|
||||
{t.Imports(), "Imports"},
|
||||
{t.Messages(), "Messages"},
|
||||
{t.Enums(), "Enums"},
|
||||
{t.Extensions(), "Extensions"},
|
||||
{t.Services(), "Services"},
|
||||
}...)
|
||||
|
||||
case protoreflect.MessageDescriptor:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("IsMapEntry"), "IsMapEntry"},
|
||||
{rv.MethodByName("Fields"), "Fields"},
|
||||
{rv.MethodByName("Oneofs"), "Oneofs"},
|
||||
{rv.MethodByName("ReservedNames"), "ReservedNames"},
|
||||
{rv.MethodByName("ReservedRanges"), "ReservedRanges"},
|
||||
{rv.MethodByName("RequiredNumbers"), "RequiredNumbers"},
|
||||
{rv.MethodByName("ExtensionRanges"), "ExtensionRanges"},
|
||||
{rv.MethodByName("Messages"), "Messages"},
|
||||
{rv.MethodByName("Enums"), "Enums"},
|
||||
{rv.MethodByName("Extensions"), "Extensions"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{t.IsMapEntry(), "IsMapEntry"},
|
||||
{t.Fields(), "Fields"},
|
||||
{t.Oneofs(), "Oneofs"},
|
||||
{t.ReservedNames(), "ReservedNames"},
|
||||
{t.ReservedRanges(), "ReservedRanges"},
|
||||
{t.RequiredNumbers(), "RequiredNumbers"},
|
||||
{t.ExtensionRanges(), "ExtensionRanges"},
|
||||
{t.Messages(), "Messages"},
|
||||
{t.Enums(), "Enums"},
|
||||
{t.Extensions(), "Extensions"},
|
||||
}...)
|
||||
|
||||
case protoreflect.EnumDescriptor:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Values"), "Values"},
|
||||
{rv.MethodByName("ReservedNames"), "ReservedNames"},
|
||||
{rv.MethodByName("ReservedRanges"), "ReservedRanges"},
|
||||
{rv.MethodByName("IsClosed"), "IsClosed"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{t.Values(), "Values"},
|
||||
{t.ReservedNames(), "ReservedNames"},
|
||||
{t.ReservedRanges(), "ReservedRanges"},
|
||||
{t.IsClosed(), "IsClosed"},
|
||||
}...)
|
||||
|
||||
case protoreflect.EnumValueDescriptor:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Number"), "Number"},
|
||||
}...)
|
||||
rs.Append(rv, attrAndName{t.Number(), "Number"})
|
||||
|
||||
case protoreflect.ServiceDescriptor:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Methods"), "Methods"},
|
||||
}...)
|
||||
rs.Append(rv, attrAndName{t.Methods(), "Methods"})
|
||||
|
||||
case protoreflect.MethodDescriptor:
|
||||
rs.Append(rv, []methodAndName{
|
||||
{rv.MethodByName("Input"), "Input"},
|
||||
{rv.MethodByName("Output"), "Output"},
|
||||
{rv.MethodByName("IsStreamingClient"), "IsStreamingClient"},
|
||||
{rv.MethodByName("IsStreamingServer"), "IsStreamingServer"},
|
||||
rs.Append(rv, []attrAndName{
|
||||
{t.Input(), "Input"},
|
||||
{t.Output(), "Output"},
|
||||
{t.IsStreamingClient(), "IsStreamingClient"},
|
||||
{t.IsStreamingServer(), "IsStreamingServer"},
|
||||
}...)
|
||||
}
|
||||
if m := rv.MethodByName("GoType"); m.IsValid() {
|
||||
rs.Append(rv, methodAndName{m, "GoType"})
|
||||
if m, ok := t.(interface{ GoType() reflect.Type }); ok {
|
||||
rs.Append(rv, attrAndName{m.GoType(), "GoType"})
|
||||
}
|
||||
}
|
||||
return start + rs.Join() + end
|
||||
@@ -297,70 +294,68 @@ func (rs *records) AppendRecs(fieldName string, newRecs [2]string) {
|
||||
rs.recs = append(rs.recs, newRecs)
|
||||
}
|
||||
|
||||
func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
|
||||
for _, a := range accessors {
|
||||
if rs.record != nil {
|
||||
rs.record(a.name)
|
||||
}
|
||||
var rv reflect.Value
|
||||
if a.method.IsValid() {
|
||||
rv = a.method.Call(nil)[0]
|
||||
}
|
||||
if v.Kind() == reflect.Struct && !rv.IsValid() {
|
||||
rv = v.FieldByName(a.name)
|
||||
}
|
||||
if !rv.IsValid() {
|
||||
panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
|
||||
}
|
||||
if _, ok := rv.Interface().(protoreflect.Value); ok {
|
||||
rv = rv.MethodByName("Interface").Call(nil)[0]
|
||||
if !rv.IsNil() {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore zero values.
|
||||
var isZero bool
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Slice:
|
||||
isZero = rv.IsNil()
|
||||
case reflect.Bool:
|
||||
isZero = rv.Bool() == false
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
isZero = rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
isZero = rv.Uint() == 0
|
||||
case reflect.String:
|
||||
isZero = rv.String() == ""
|
||||
}
|
||||
if n, ok := rv.Interface().(list); ok {
|
||||
isZero = n.Len() == 0
|
||||
}
|
||||
if isZero {
|
||||
continue
|
||||
}
|
||||
|
||||
// Format the value.
|
||||
var s string
|
||||
v := rv.Interface()
|
||||
switch v := v.(type) {
|
||||
case list:
|
||||
s = formatListOpt(v, false, rs.allowMulti)
|
||||
case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
|
||||
s = string(v.(protoreflect.Descriptor).Name())
|
||||
case protoreflect.Descriptor:
|
||||
s = string(v.FullName())
|
||||
case string:
|
||||
s = strconv.Quote(v)
|
||||
case []byte:
|
||||
s = fmt.Sprintf("%q", v)
|
||||
default:
|
||||
s = fmt.Sprint(v)
|
||||
}
|
||||
rs.recs = append(rs.recs, [2]string{a.name, s})
|
||||
func (rs *records) Append(v reflect.Value, results ...attrAndName) {
|
||||
for _, r := range results {
|
||||
rs.appendAttribute(v, r.name, r.attr)
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *records) appendAttribute(val reflect.Value, name string, attrVal any) {
|
||||
if rs.record != nil {
|
||||
rs.record(name)
|
||||
}
|
||||
if attrVal == nil {
|
||||
return
|
||||
}
|
||||
rv := reflect.ValueOf(attrVal)
|
||||
if _, ok := rv.Interface().(protoreflect.Value); ok {
|
||||
rv = rv.MethodByName("Interface").Call(nil)[0]
|
||||
if !rv.IsNil() {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore zero values.
|
||||
var isZero bool
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Slice:
|
||||
isZero = rv.IsNil()
|
||||
case reflect.Bool:
|
||||
isZero = rv.Bool() == false
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
isZero = rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
isZero = rv.Uint() == 0
|
||||
case reflect.String:
|
||||
isZero = rv.String() == ""
|
||||
}
|
||||
if n, ok := rv.Interface().(list); ok {
|
||||
isZero = n.Len() == 0
|
||||
}
|
||||
if isZero {
|
||||
return
|
||||
}
|
||||
|
||||
// Format the value.
|
||||
var s string
|
||||
v := rv.Interface()
|
||||
switch v := v.(type) {
|
||||
case list:
|
||||
s = formatListOpt(v, false, rs.allowMulti)
|
||||
case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
|
||||
s = string(v.(protoreflect.Descriptor).Name())
|
||||
case protoreflect.Descriptor:
|
||||
s = string(v.FullName())
|
||||
case string:
|
||||
s = strconv.Quote(v)
|
||||
case []byte:
|
||||
s = fmt.Sprintf("%q", v)
|
||||
default:
|
||||
s = fmt.Sprint(v)
|
||||
}
|
||||
rs.recs = append(rs.recs, [2]string{name, s})
|
||||
}
|
||||
|
||||
func (rs *records) Join() string {
|
||||
var ss []string
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ const (
|
||||
Major = 1
|
||||
Minor = 36
|
||||
Patch = 11
|
||||
PreRelease = ""
|
||||
PreRelease = "devel"
|
||||
)
|
||||
|
||||
// String formats the version string for this module in semver format.
|
||||
|
||||
+1
@@ -201,6 +201,7 @@ func (r descsByName) initExtensionDeclarations(xds []*descriptorpb.FieldDescript
|
||||
return nil, err
|
||||
}
|
||||
x.L1.EditionFeatures = mergeEditionFeatures(parent, xd.GetOptions().GetFeatures())
|
||||
x.L2.IsProto3Optional = xd.GetProto3Optional()
|
||||
if opts := xd.GetOptions(); opts != nil {
|
||||
opts = proto.Clone(opts).(*descriptorpb.FieldOptions)
|
||||
x.L2.Options = func() protoreflect.ProtoMessage { return opts }
|
||||
|
||||
Reference in New Issue
Block a user