vendor: update buildkit to v0.32.0-rc1

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-07-22 15:27:47 -07:00
parent efd9aa1dea
commit 0cf7592d41
464 changed files with 19941 additions and 15772 deletions
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -19,4 +19,4 @@
package grpc
// Version is the current grpc version.
const Version = "1.81.1"
const Version = "1.82.1"
+180 -50
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 }