mirror of
https://github.com/docker/cli.git
synced 2026-08-28 02:24:15 -05:00
Merge pull request #7240 from thaJeztah/bump_grpc
vendor: google.golang.org/grpc v1.83.2
This commit is contained in:
+1
-1
@@ -106,6 +106,6 @@ require (
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/grpc v1.82.1 // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
+2
-2
@@ -281,8 +281,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
|
||||
+4
-19
@@ -24,12 +24,10 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/balancer"
|
||||
@@ -1573,26 +1571,13 @@ func (ac *addrConn) createTransport(ctx context.Context, addr resolver.Address,
|
||||
// to the provided transport.GoAwayInfo, as specified by gRFC A94:
|
||||
// https://github.com/grpc/proposal/blob/master/A94-grpc-subchannel-disconnections-metrics.md
|
||||
func disconnectErrorString(info transport.GoAwayInfo) string {
|
||||
err := info.Err
|
||||
var sysErr syscall.Errno
|
||||
switch {
|
||||
case info.Reason != transport.GoAwayInvalid:
|
||||
if info.Reason != transport.GoAwayInvalid {
|
||||
return fmt.Sprintf("GOAWAY %s", info.GoAwayCode.String())
|
||||
case err == nil:
|
||||
return "unknown"
|
||||
case errors.Is(err, context.Canceled):
|
||||
return "subchannel shutdown"
|
||||
case errors.Is(err, syscall.ECONNRESET):
|
||||
return "connection reset"
|
||||
case errors.Is(err, syscall.ETIMEDOUT), errors.Is(err, context.DeadlineExceeded), errors.Is(err, os.ErrDeadlineExceeded):
|
||||
return "connection timed out"
|
||||
case errors.Is(err, syscall.ECONNABORTED):
|
||||
return "connection aborted"
|
||||
case errors.As(err, &sysErr):
|
||||
return "socket error"
|
||||
default:
|
||||
}
|
||||
if info.Err == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return disconnectErrorLabel(info.Err)
|
||||
}
|
||||
|
||||
// startHealthCheck starts the health checking stream (RPC) to watch the health
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//go:build !plan9
|
||||
|
||||
/*
|
||||
*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// disconnectErrorLabel returns the grpc.disconnect_error metric label for a
|
||||
// transport error, as specified by gRFC A94.
|
||||
func disconnectErrorLabel(err error) string {
|
||||
var sysErr syscall.Errno
|
||||
switch {
|
||||
case errors.Is(err, context.Canceled):
|
||||
return "subchannel shutdown"
|
||||
case errors.Is(err, syscall.ECONNRESET):
|
||||
return "connection reset"
|
||||
case errors.Is(err, syscall.ETIMEDOUT), errors.Is(err, context.DeadlineExceeded), errors.Is(err, os.ErrDeadlineExceeded):
|
||||
return "connection timed out"
|
||||
case errors.Is(err, syscall.ECONNABORTED):
|
||||
return "connection aborted"
|
||||
case errors.As(err, &sysErr):
|
||||
return "socket error"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
// disconnectErrorLabel returns the grpc.disconnect_error metric label for a
|
||||
// transport error, as specified by gRFC A94. syscall.Errno does not exist on
|
||||
// plan9, so only the portable classifications are available.
|
||||
func disconnectErrorLabel(err error) string {
|
||||
switch {
|
||||
case errors.Is(err, context.Canceled):
|
||||
return "subchannel shutdown"
|
||||
case errors.Is(err, context.DeadlineExceeded), errors.Is(err, os.ErrDeadlineExceeded):
|
||||
return "connection timed out"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -150,8 +150,18 @@ var (
|
||||
// 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.
|
||||
// TODO: Remove this env var once v1.83.0 is released.
|
||||
ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 100, 1, 10000)
|
||||
|
||||
// EnableReceiveBufferCompaction enables the compaction of data buffers
|
||||
// to reduce the number of buffers in the receive buffer.
|
||||
//
|
||||
// This environment variable serves as an escape hatch to disable the
|
||||
// feature if unforeseen issues arise, and it will be removed in a future
|
||||
// release.
|
||||
//
|
||||
// TODO: Remove this env var once v1.85.0 is released.
|
||||
EnableReceiveBufferCompaction = boolFromEnv("GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION", true)
|
||||
)
|
||||
|
||||
func boolFromEnv(envVar string, def bool) bool {
|
||||
|
||||
+3
-4
@@ -69,9 +69,8 @@ var (
|
||||
// https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md
|
||||
XDSSPIFFEEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_MTLS_SPIFFE", false)
|
||||
|
||||
// XDSHTTPConnectEnabled is true if gRPC should parse custom Metadata
|
||||
// configuring use of an HTTP CONNECT proxy via xDS from cluster resources.
|
||||
// For more details, see:
|
||||
// XDSHTTPConnectEnabled controls support for dynamic HTTP CONNECT proxying
|
||||
// configured via the xDS control plane. For more details, see:
|
||||
// https://github.com/grpc/proposal/blob/master/A86-xds-http-connect.md
|
||||
XDSHTTPConnectEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_HTTP_CONNECT", false)
|
||||
|
||||
@@ -88,7 +87,7 @@ var (
|
||||
// XDSORCAToLRSPropEnabled controls whether ORCA metrics are explicitly
|
||||
// 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)
|
||||
XDSORCAToLRSPropEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_ORCA_LRS_PROPAGATION", true)
|
||||
|
||||
// XDSClientExtProcEnabled indicates whether ExtProc filter is enabled on
|
||||
// the client side. For more details, see:
|
||||
|
||||
+26
@@ -20,10 +20,15 @@ package grpcsync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"google.golang.org/grpc/internal/buffer"
|
||||
)
|
||||
|
||||
// ErrSerializerClosed is returned by ScheduleAndWait if the CallbackSerializer
|
||||
// was closed before the callback could be scheduled.
|
||||
var ErrSerializerClosed = errors.New("callback serializer is closed")
|
||||
|
||||
// CallbackSerializer provides a mechanism to schedule callbacks in a
|
||||
// synchronized manner. It provides a FIFO guarantee on the order of execution
|
||||
// of scheduled callbacks. New callbacks can be scheduled by invoking the
|
||||
@@ -77,6 +82,27 @@ func (cs *CallbackSerializer) ScheduleOr(f func(ctx context.Context), onFailure
|
||||
}
|
||||
}
|
||||
|
||||
// ScheduleAndWait schedules the provided callback function f to be executed in
|
||||
// the order it was added and blocks until f has run. If the context passed to
|
||||
// NewCallbackSerializer was canceled before this method is called, f is not run
|
||||
// and ScheduleAndWait returns ErrSerializerClosed.
|
||||
//
|
||||
// Callbacks are expected to honor the context when performing any blocking
|
||||
// operations, and should return early when the context is canceled.
|
||||
func (cs *CallbackSerializer) ScheduleAndWait(f func(ctx context.Context)) error {
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
cs.ScheduleOr(func(ctx context.Context) {
|
||||
f(ctx)
|
||||
close(done)
|
||||
}, func() {
|
||||
err = ErrSerializerClosed
|
||||
close(done)
|
||||
})
|
||||
<-done
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *CallbackSerializer) run(ctx context.Context) {
|
||||
defer close(cs.done)
|
||||
|
||||
|
||||
+14
@@ -26,12 +26,26 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
goPageSize = 4 * 1024 // 4KiB. N.B. this must be a power of 2.
|
||||
)
|
||||
|
||||
var (
|
||||
// BufferPoolingThreshold is the minimum size of a buffer that can be pooled.
|
||||
// This is used to determine whether to pool buffers or allocate them directly.
|
||||
BufferPoolingThreshold = 1 << 10
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.SetBufferPoolingThresholdForTesting = func(threshold int) {
|
||||
BufferPoolingThreshold = threshold
|
||||
}
|
||||
}
|
||||
|
||||
var uintSize = bits.UintSize // use a variable for mocking during tests.
|
||||
|
||||
// bufferPool is a copy of the public bufferPool interface used to avoid
|
||||
|
||||
+1
-77
@@ -24,7 +24,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/internal/serviceconfig"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
@@ -52,82 +51,7 @@ type RPCConfig struct {
|
||||
Context context.Context
|
||||
MethodConfig serviceconfig.MethodConfig // configuration to use for this RPC
|
||||
OnCommitted func() // Called when the RPC has been committed (retries no longer possible)
|
||||
Interceptor ClientInterceptor
|
||||
}
|
||||
|
||||
// ClientStream is the same as grpc.ClientStream, but defined here for circular
|
||||
// dependency reasons.
|
||||
type ClientStream interface {
|
||||
// Header returns the header metadata received from the server if there
|
||||
// is any. It blocks if the metadata is not ready to read.
|
||||
Header() (metadata.MD, error)
|
||||
// Trailer returns the trailer metadata from the server, if there is any.
|
||||
// It must only be called after stream.CloseAndRecv has returned, or
|
||||
// stream.Recv has returned a non-nil error (including io.EOF).
|
||||
Trailer() metadata.MD
|
||||
// CloseSend closes the send direction of the stream. It closes the stream
|
||||
// when non-nil error is met. It is also not safe to call CloseSend
|
||||
// concurrently with SendMsg.
|
||||
CloseSend() error
|
||||
// Context returns the context for this stream.
|
||||
//
|
||||
// It should not be called until after Header or RecvMsg has returned. Once
|
||||
// called, subsequent client-side retries are disabled.
|
||||
Context() context.Context
|
||||
// SendMsg is generally called by generated code. On error, SendMsg aborts
|
||||
// the stream. If the error was generated by the client, the status is
|
||||
// returned directly; otherwise, io.EOF is returned and the status of
|
||||
// the stream may be discovered using RecvMsg.
|
||||
//
|
||||
// SendMsg blocks until:
|
||||
// - There is sufficient flow control to schedule m with the transport, or
|
||||
// - The stream is done, or
|
||||
// - The stream breaks.
|
||||
//
|
||||
// SendMsg does not wait until the message is received by the server. An
|
||||
// untimely stream closure may result in lost messages. To ensure delivery,
|
||||
// users should ensure the RPC completed successfully using RecvMsg.
|
||||
//
|
||||
// It is safe to have a goroutine calling SendMsg and another goroutine
|
||||
// calling RecvMsg on the same stream at the same time, but it is not safe
|
||||
// to call SendMsg on the same stream in different goroutines. It is also
|
||||
// not safe to call CloseSend concurrently with SendMsg.
|
||||
SendMsg(m any) error
|
||||
// RecvMsg blocks until it receives a message into m or the stream is
|
||||
// done. It returns io.EOF when the stream completes successfully. On
|
||||
// any other error, the stream is aborted and the error contains the RPC
|
||||
// status.
|
||||
//
|
||||
// It is safe to have a goroutine calling SendMsg and another goroutine
|
||||
// calling RecvMsg on the same stream at the same time, but it is not
|
||||
// safe to call RecvMsg on the same stream in different goroutines.
|
||||
RecvMsg(m any) error
|
||||
}
|
||||
|
||||
// ClientInterceptor is an interceptor for gRPC client streams.
|
||||
type ClientInterceptor interface {
|
||||
// NewStream creates a ClientStream for an RPC.
|
||||
//
|
||||
// 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.
|
||||
Close()
|
||||
Interceptor any
|
||||
}
|
||||
|
||||
// ServerInterceptor is an interceptor for incoming RPC's on gRPC server side.
|
||||
|
||||
+2
-3
@@ -39,9 +39,8 @@ const nonGRPCDataMaxLen = 1024
|
||||
type ClientStream struct {
|
||||
Stream // Embed for common stream functionality.
|
||||
|
||||
ct *http2Client
|
||||
done chan struct{} // closed at the end of stream to unblock writers.
|
||||
doneFunc func() // invoked at the end of stream.
|
||||
ct *http2Client
|
||||
done chan struct{} // closed at the end of stream to unblock writers.
|
||||
|
||||
headerChan chan struct{} // closed to indicate the end of header metadata.
|
||||
header metadata.MD // the received header metadata
|
||||
|
||||
+1
-1
@@ -424,7 +424,7 @@ func (ht *serverHandlerTransport) HandleStreams(ctx context.Context, startStream
|
||||
st: ht,
|
||||
headerWireLength: 0, // won't have access to header wire length until golang/go#18997.
|
||||
}
|
||||
s.Stream.buf.init()
|
||||
s.Stream.buf.init(ht.bufferPool)
|
||||
s.readRequester = s
|
||||
s.trReader = transportReader{
|
||||
reader: recvBufferReader{ctx: s.ctx, ctxDone: s.ctx.Done(), recv: &s.buf},
|
||||
|
||||
+1
-5
@@ -498,10 +498,9 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr, handler s
|
||||
ct: t,
|
||||
done: make(chan struct{}),
|
||||
headerChan: make(chan struct{}),
|
||||
doneFunc: callHdr.DoneFunc,
|
||||
statsHandler: handler,
|
||||
}
|
||||
s.Stream.buf.init()
|
||||
s.Stream.buf.init(t.bufferPool)
|
||||
s.Stream.wq.init(defaultWriteQuota, s.done)
|
||||
s.readRequester = s
|
||||
// The client side stream context should have exactly the same life cycle with the user provided context.
|
||||
@@ -998,9 +997,6 @@ func (t *http2Client) closeStream(s *ClientStream, err error, rst bool, rstCode
|
||||
t.controlBuf.executeAndPut(addBackStreamQuota, cleanup)
|
||||
// This will unblock write.
|
||||
close(s.done)
|
||||
if s.doneFunc != nil {
|
||||
s.doneFunc()
|
||||
}
|
||||
}
|
||||
|
||||
// Close kicks off the shutdown process of the transport. This should be called
|
||||
|
||||
+7
-1
@@ -407,7 +407,7 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
|
||||
st: t,
|
||||
headerWireLength: int(frame.Header().Length),
|
||||
}
|
||||
s.Stream.buf.init()
|
||||
s.Stream.buf.init(t.bufferPool)
|
||||
var (
|
||||
// if false, content-type was missing or invalid
|
||||
isGRPC = false
|
||||
@@ -522,6 +522,12 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade
|
||||
delete(mdata, "host")
|
||||
}
|
||||
|
||||
// If :authority is still missing, i.e. no host or :authority header is
|
||||
// present, reject the request as invalid.
|
||||
if len(mdata[":authority"]) == 0 {
|
||||
t.writeEarlyAbort(streamID, s.contentSubtype, status.New(codes.Internal, "no host or :authority header present"), http.StatusBadRequest, !frame.StreamEnded())
|
||||
return nil
|
||||
}
|
||||
if frame.StreamEnded() {
|
||||
// s is just created by the caller. No lock needed.
|
||||
s.state = streamReadDone
|
||||
|
||||
+104
-8
@@ -30,11 +30,14 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/internal/channelz"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
imem "google.golang.org/grpc/internal/mem"
|
||||
"google.golang.org/grpc/internal/transport/internal"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/mem"
|
||||
@@ -45,7 +48,30 @@ import (
|
||||
"google.golang.org/grpc/tap"
|
||||
)
|
||||
|
||||
const logLevel = 2
|
||||
const (
|
||||
logLevel = 2
|
||||
// recvMsgSize estimates the memory overhead of a recvMsg in the backlog.
|
||||
// It accounts for the recvMsg struct itself and the slice header of the
|
||||
// underlying buffer's data.
|
||||
recvMsgSize = int(unsafe.Sizeof(recvMsg{}) + unsafe.Sizeof([]byte{}))
|
||||
|
||||
// utilizationFactor controls when we consider memory utilization acceptable.
|
||||
// When backlogHeapSize / payloadSize <= utilizationFactor (meaning at least
|
||||
// 50% of the heap memory is actual payload data), compaction is skipped.
|
||||
utilizationFactor = 2
|
||||
)
|
||||
|
||||
var (
|
||||
// compactionThreshold is approx 57KB (on 64-bit systems). It allows
|
||||
// accumulating up to 1024 1-byte payloads before triggering compaction.
|
||||
//
|
||||
// Because individual payloads <= 1024 bytes are allocated on the heap
|
||||
// outside mem.BufferPool, waiting for at least 1024 bytes to accumulate
|
||||
// ensures that compaction coalesces those small heap allocations into a
|
||||
// single large buffer from mem.BufferPool, enabling buffer reuse while
|
||||
// avoiding frequent copying for small bursts of frames.
|
||||
compactionThreshold = imem.BufferPoolingThreshold * (recvMsgSize + 1)
|
||||
)
|
||||
|
||||
func init() {
|
||||
internal.TimeNowFunc = func() int64 { return time.Now().UnixNano() }
|
||||
@@ -71,23 +97,31 @@ type recvBuffer struct {
|
||||
c chan recvMsg
|
||||
mu sync.Mutex
|
||||
backlog []recvMsg
|
||||
err error
|
||||
// uncompactedSuffixLen tracks the number of consecutive data messages at
|
||||
// the tail of backlog that have not been compacted.
|
||||
uncompactedSuffixLen int
|
||||
// uncompactedBytes tracks the total payload bytes across the trailing
|
||||
// uncompactedSuffixLen messages.
|
||||
uncompactedBytes int
|
||||
err error
|
||||
bufPool mem.BufferPool
|
||||
}
|
||||
|
||||
// init allows a recvBuffer to be initialized in-place, which is useful
|
||||
// for resetting a buffer or for avoiding a heap allocation when the buffer
|
||||
// is embedded in another struct.
|
||||
func (b *recvBuffer) init() {
|
||||
func (b *recvBuffer) init(pool mem.BufferPool) {
|
||||
b.c = make(chan recvMsg, 1)
|
||||
b.bufPool = pool
|
||||
}
|
||||
|
||||
func (b *recvBuffer) put(r recvMsg) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.err != nil {
|
||||
// drop the buffer on the floor. Since b.err is not nil, any subsequent reads
|
||||
// will always return an error, making this buffer inaccessible.
|
||||
r.buffer.Free()
|
||||
b.mu.Unlock()
|
||||
// An error had occurred earlier, don't accept more
|
||||
// data or errors.
|
||||
return
|
||||
@@ -96,13 +130,70 @@ func (b *recvBuffer) put(r recvMsg) {
|
||||
if len(b.backlog) == 0 {
|
||||
select {
|
||||
case b.c <- r:
|
||||
b.mu.Unlock()
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
b.backlog = append(b.backlog, r)
|
||||
b.mu.Unlock()
|
||||
b.compactBacklogLocked(r)
|
||||
}
|
||||
|
||||
func (b *recvBuffer) compactBacklogLocked(r recvMsg) {
|
||||
if !envconfig.EnableReceiveBufferCompaction {
|
||||
return
|
||||
}
|
||||
if r.buffer == nil {
|
||||
b.uncompactedBytes = 0
|
||||
b.uncompactedSuffixLen = 0
|
||||
return
|
||||
}
|
||||
|
||||
b.uncompactedSuffixLen++
|
||||
b.uncompactedBytes += r.buffer.Len()
|
||||
backlogHeapSize := b.uncompactedSuffixLen*recvMsgSize + b.uncompactedBytes
|
||||
|
||||
// If the memory overhead is less than 50% of the heap usage (e.g., because
|
||||
// a large DATA frame arrived), the average message size in the suffix is
|
||||
// large enough that memory bloat is not a concern. Reset suffix tracking.
|
||||
if backlogHeapSize <= utilizationFactor*b.uncompactedBytes {
|
||||
b.uncompactedBytes = 0
|
||||
b.uncompactedSuffixLen = 0
|
||||
return
|
||||
}
|
||||
// Avoid compacting too frequently for short bursts of small frames.
|
||||
// Wait until we have accumulated at least ~1024 small messages (~57 KB).
|
||||
if backlogHeapSize <= compactionThreshold {
|
||||
// Still can accumulate more payloads.
|
||||
return
|
||||
}
|
||||
|
||||
// Since the memory utilization is less than 50%, the average payload size
|
||||
// of each recvMsg must be less than recvMsgSize (approx 56 bytes).
|
||||
// In the worst case for bytes copied (where the average payload is just
|
||||
// below recvMsgSize), compaction will occur once every:
|
||||
// compactionThreshold / (recvMsgSize + avg_payload) = ~520 messages,
|
||||
// copying ~29KB of data.
|
||||
|
||||
start := 0
|
||||
newBuf := b.bufPool.Get(b.uncompactedBytes)
|
||||
startIdx := len(b.backlog) - b.uncompactedSuffixLen
|
||||
|
||||
for i := startIdx; i < len(b.backlog); i++ {
|
||||
m := b.backlog[i]
|
||||
b.backlog[i] = recvMsg{}
|
||||
start += copy((*newBuf)[start:], m.buffer.ReadOnlyData())
|
||||
m.buffer.Free()
|
||||
}
|
||||
b.backlog[startIdx] = recvMsg{
|
||||
buffer: mem.NewBuffer(newBuf, b.bufPool),
|
||||
}
|
||||
b.backlog = b.backlog[:startIdx+1]
|
||||
// After compaction, the suffix is replaced with a single message containing
|
||||
// the combined payload. The new utilization is close to 1.0 (overhead of
|
||||
// one recvMsg relative to the large compacted payload), which is well
|
||||
// below the utilization factor of 2.
|
||||
b.uncompactedBytes = 0
|
||||
b.uncompactedSuffixLen = 0
|
||||
}
|
||||
|
||||
func (b *recvBuffer) load() {
|
||||
@@ -110,6 +201,13 @@ func (b *recvBuffer) load() {
|
||||
if len(b.backlog) > 0 {
|
||||
select {
|
||||
case b.c <- b.backlog[0]:
|
||||
// backlog[0] is only part of the tracked uncompacted suffix if the
|
||||
// entire backlog currently consists of the suffix. If an earlier
|
||||
// compaction or reset occurred, backlog[0] is already compacted.
|
||||
if envconfig.EnableReceiveBufferCompaction && b.uncompactedSuffixLen == len(b.backlog) {
|
||||
b.uncompactedSuffixLen--
|
||||
b.uncompactedBytes -= b.backlog[0].buffer.Len()
|
||||
}
|
||||
b.backlog[0] = recvMsg{}
|
||||
b.backlog = b.backlog[1:]
|
||||
default:
|
||||
@@ -594,8 +692,6 @@ type CallHdr struct {
|
||||
|
||||
PreviousAttempts int // value of grpc-previous-rpc-attempts header to set
|
||||
|
||||
DoneFunc func() // called when the stream is finished
|
||||
|
||||
// Authority is used to explicitly override the `:authority` header.
|
||||
//
|
||||
// This value comes from one of two sources:
|
||||
|
||||
-4
@@ -59,10 +59,6 @@ func init() {
|
||||
internal.SetDefaultBufferPool = func(pool BufferPool) {
|
||||
defaultBufferPool = pool
|
||||
}
|
||||
|
||||
internal.SetBufferPoolingThresholdForTesting = func(threshold int) {
|
||||
bufferPoolingThreshold = threshold
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultBufferPool returns the current default buffer pool. It is a BufferPool
|
||||
|
||||
+3
-3
@@ -29,6 +29,8 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"google.golang.org/grpc/internal/mem"
|
||||
)
|
||||
|
||||
// A Buffer represents a reference counted piece of data (in bytes) that can be
|
||||
@@ -63,8 +65,6 @@ type Buffer interface {
|
||||
}
|
||||
|
||||
var (
|
||||
bufferPoolingThreshold = 1 << 10
|
||||
|
||||
bufferObjectPool = sync.Pool{New: func() any { return new(buffer) }}
|
||||
)
|
||||
|
||||
@@ -72,7 +72,7 @@ var (
|
||||
// equal to the threshold for buffer pooling. This is used to determine whether
|
||||
// to pool buffers or allocate them directly.
|
||||
func IsBelowBufferPoolingThreshold(size int) bool {
|
||||
return size <= bufferPoolingThreshold
|
||||
return size <= mem.BufferPoolingThreshold
|
||||
}
|
||||
|
||||
type buffer struct {
|
||||
|
||||
+25
-10
@@ -201,6 +201,15 @@ func endOfClientStream(cc *ClientConn, err error, opts ...CallOption) {
|
||||
}
|
||||
}
|
||||
|
||||
// clientInterceptor is structurally identical to the ClientInterceptor defined
|
||||
// in internal/xds/httpfilter/httpfilter.go. It is defined locally here so that
|
||||
// we can type-assert the generic Interceptor field in iresolver.RPCConfig
|
||||
// without introducing a dependency on xDS packages.
|
||||
type clientInterceptor interface {
|
||||
NewStream(ctx context.Context, ri iresolver.RPCInfo, newStream func(ctx context.Context, opts ...CallOption) (ClientStream, error), opts ...CallOption) (ClientStream, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) {
|
||||
if channelz.IsOn() {
|
||||
cc.incrCallsStarted()
|
||||
@@ -244,8 +253,11 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
|
||||
|
||||
mc := &emptyMethodConfig
|
||||
var onCommit func()
|
||||
newStream := func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
|
||||
return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, nameResolutionDelayed, opts...)
|
||||
newStream := func(ctx context.Context, filterOpts ...CallOption) (ClientStream, error) {
|
||||
if filterOpts != nil {
|
||||
opts = combine(opts, filterOpts)
|
||||
}
|
||||
return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, nameResolutionDelayed, opts...)
|
||||
}
|
||||
|
||||
rpcInfo := iresolver.RPCInfo{Context: ctx, Method: method}
|
||||
@@ -270,20 +282,24 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
|
||||
if rpcConfig.Interceptor != nil {
|
||||
rpcInfo.Context = nil
|
||||
ns := newStream
|
||||
newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
|
||||
cs, err := rpcConfig.Interceptor.NewStream(ctx, rpcInfo, done, ns)
|
||||
if err != nil {
|
||||
return nil, toRPCErr(err)
|
||||
if interceptor, ok := rpcConfig.Interceptor.(clientInterceptor); ok {
|
||||
newStream = func(ctx context.Context, filterOpts ...CallOption) (ClientStream, error) {
|
||||
cs, err := interceptor.NewStream(ctx, rpcInfo, ns, filterOpts...)
|
||||
if err != nil {
|
||||
return nil, toRPCErr(err)
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
return cs, nil
|
||||
} else {
|
||||
return nil, status.Errorf(codes.Internal, "invalid client interceptor type %T", rpcConfig.Interceptor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newStream(ctx, func() {})
|
||||
return newStream(ctx)
|
||||
}
|
||||
|
||||
func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc *serviceconfig.MethodConfig, onCommit, doneFunc func(), nameResolutionDelayed bool, opts ...CallOption) (_ iresolver.ClientStream, err error) {
|
||||
func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc *serviceconfig.MethodConfig, onCommit func(), nameResolutionDelayed bool, opts ...CallOption) (_ ClientStream, err error) {
|
||||
callInfo := defaultCallInfo()
|
||||
if mc.WaitForReady != nil {
|
||||
callInfo.failFast = !*mc.WaitForReady
|
||||
@@ -321,7 +337,6 @@ func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *Client
|
||||
Host: cc.authority,
|
||||
Method: method,
|
||||
ContentSubtype: callInfo.contentSubtype,
|
||||
DoneFunc: doneFunc,
|
||||
Authority: callInfo.authority,
|
||||
}
|
||||
if allowed := callInfo.acceptedResponseCompressors; len(allowed) > 0 {
|
||||
|
||||
+1
-1
@@ -19,4 +19,4 @@
|
||||
package grpc
|
||||
|
||||
// Version is the current grpc version.
|
||||
const Version = "1.82.1"
|
||||
const Version = "1.83.2"
|
||||
|
||||
Vendored
+1
-1
@@ -445,7 +445,7 @@ google.golang.org/genproto/googleapis/api/httpbody
|
||||
## explicit; go 1.25.0
|
||||
google.golang.org/genproto/googleapis/rpc/errdetails
|
||||
google.golang.org/genproto/googleapis/rpc/status
|
||||
# google.golang.org/grpc v1.82.1
|
||||
# google.golang.org/grpc v1.83.2
|
||||
## explicit; go 1.25.0
|
||||
google.golang.org/grpc
|
||||
google.golang.org/grpc/attributes
|
||||
|
||||
Reference in New Issue
Block a user