diff --git a/go.mod b/go.mod index 6bd513be..0090fb8d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/prometheus/client_golang v1.25.0 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 - google.golang.org/grpc v1.84.0 + google.golang.org/grpc v1.85.0 k8s.io/apimachinery v0.37.1 k8s.io/kubelet v0.37.1 ) @@ -24,7 +24,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/logr v1.4.4 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -39,10 +39,10 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/net v0.59.0 // indirect + golang.org/x/net v0.60.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect diff --git a/go.sum b/go.sum index 91f2cfa6..5dfa92a5 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= +github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -87,8 +87,8 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= -golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= @@ -97,10 +97,10 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= -google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 h1:utmqiUzZAEINbLmRyAt1QjVkMyLQ+lvIwCpLaB1rZfw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.85.0 h1:BPBCfuVRQ1qvBhKDOq9uvQllMvO59rKsYhXpp7Dd8vw= +google.golang.org/grpc v1.85.0/go.mod h1:0rQrOD58yjal/0EaVbGBANindixsCZNQPDV3VKtaKuQ= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/vendor/github.com/go-logr/logr/context_noslog.go b/vendor/github.com/go-logr/logr/context_noslog.go index f012f9a1..0a3d1a12 100644 --- a/vendor/github.com/go-logr/logr/context_noslog.go +++ b/vendor/github.com/go-logr/logr/context_noslog.go @@ -1,5 +1,4 @@ //go:build !go1.21 -// +build !go1.21 /* Copyright 2019 The logr Authors. diff --git a/vendor/github.com/go-logr/logr/context_slog.go b/vendor/github.com/go-logr/logr/context_slog.go index 065ef0b8..c69eb01b 100644 --- a/vendor/github.com/go-logr/logr/context_slog.go +++ b/vendor/github.com/go-logr/logr/context_slog.go @@ -1,5 +1,4 @@ //go:build go1.21 -// +build go1.21 /* Copyright 2019 The logr Authors. diff --git a/vendor/github.com/go-logr/logr/sloghandler.go b/vendor/github.com/go-logr/logr/sloghandler.go index 82d1ba49..befaf551 100644 --- a/vendor/github.com/go-logr/logr/sloghandler.go +++ b/vendor/github.com/go-logr/logr/sloghandler.go @@ -1,5 +1,4 @@ //go:build go1.21 -// +build go1.21 /* Copyright 2023 The logr Authors. diff --git a/vendor/github.com/go-logr/logr/slogr.go b/vendor/github.com/go-logr/logr/slogr.go index 28a83d02..bfe80eb8 100644 --- a/vendor/github.com/go-logr/logr/slogr.go +++ b/vendor/github.com/go-logr/logr/slogr.go @@ -1,5 +1,4 @@ //go:build go1.21 -// +build go1.21 /* Copyright 2023 The logr Authors. diff --git a/vendor/github.com/go-logr/logr/slogsink.go b/vendor/github.com/go-logr/logr/slogsink.go index 4060fcbc..ab76ea99 100644 --- a/vendor/github.com/go-logr/logr/slogsink.go +++ b/vendor/github.com/go-logr/logr/slogsink.go @@ -1,5 +1,4 @@ //go:build go1.21 -// +build go1.21 /* Copyright 2023 The logr Authors. diff --git a/vendor/golang.org/x/net/http2/flow.go b/vendor/golang.org/x/net/http2/flow.go index b7dbd186..5066a399 100644 --- a/vendor/golang.org/x/net/http2/flow.go +++ b/vendor/golang.org/x/net/http2/flow.go @@ -10,6 +10,9 @@ package http2 // flow control window update. const inflowMinRefresh = 4 << 10 +// maxFlowWindow is the maximum size of a flow control window. +const maxFlowWindow = (1 << 31) - 1 + // inflow accounts for an inbound flow control window. // It tracks both the latest window sent to the peer (used for enforcement) // and the accumulated unsent window. @@ -74,47 +77,99 @@ func takeInflows(f1, f2 *inflow, n uint32) bool { return true } -// outflow is the outbound flow control window's size. +// connOutflow is connection-level outbound flow control. +type connOutflow struct { + initial int32 // SETTINGS_INITIAL_WINDOW_SIZE, changes with settings updates + n int32 // connection-level flow control window + flowErr bool // set when a flow control error is encountered +} + +func (f *connOutflow) init() { + f.initial = initialWindowSize // initial stream window size + f.n = initialWindowSize // current connection window size +} + +func (f *connOutflow) changeInitialWindowSize(size int64) bool { + if size > maxFlowWindow { + f.flowErr = true + return false + } + f.initial = int32(size) + return true +} + +func (f *connOutflow) add(n int32) bool { + sum := int64(f.n) + int64(n) + if sum > maxFlowWindow { + f.flowErr = true + return false + } + f.n += n + return true +} + +// outflow is the stream-level outbound flow control window's size. type outflow struct { _ incomparable - // n is the number of DATA bytes we're allowed to send. - // An outflow is kept both on a conn and a per-stream. - n int32 + // delta is the difference between the stream's flow control window and + // the connection's initial window size (conn.initial). + // + // Another view is that delta is the number of flow control bytes provided to this + // stream in WINDOW_UPDATE frames, less the number of bytes sent on the stream. + delta int32 // conn points to the shared connection-level outflow that is - // shared by all streams on that conn. It is nil for the outflow - // that's on the conn directly. - conn *outflow + // shared by all streams on that conn. + conn *connOutflow } -func (f *outflow) setConnFlow(cf *outflow) { f.conn = cf } - -func (f *outflow) available() int32 { - n := f.n - if f.conn != nil && f.conn.n < n { - n = f.conn.n +func (f *outflow) available() (int32, bool) { + if f.conn == nil { + return maxFlowWindow, true // only happens in tests } - return n + if f.conn.flowErr { + // Block all sending once any stream observes a flow control error. + return 0, false + } + n := int64(f.conn.initial) + int64(f.delta) + if n > maxFlowWindow { + f.conn.flowErr = true + return 0, false + } + return min(int32(n), f.conn.n), true } func (f *outflow) take(n int32) { - if n > f.available() { - panic("internal error: took too much") + if f.conn == nil { + return // only happens in tests } - f.n -= n - if f.conn != nil { - f.conn.n -= n + avail, _ := f.available() + if n > avail { + panic("internal error: took too much") } + f.delta -= n + f.conn.n -= n } // add adds n bytes (positive or negative) to the flow control window. // It returns false if the sum would exceed 2^31-1. func (f *outflow) add(n int32) bool { - sum := f.n + n - if (sum > n) == (f.n > 0) { - f.n = sum - return true + if f.conn == nil { + return true // only happens in tests + } + avail := int64(f.conn.initial) + int64(f.delta) + if avail > maxFlowWindow { + // An earlier change to the initial window pushed this stream over the limit. + // This is a connection-level flow control error. + f.conn.flowErr = true + return false } - return false + if avail+int64(n) > maxFlowWindow { + // This update would push the stream over the limit. + // This is a stream-level flow control error. + return false + } + f.delta += n + return true } diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index be75badc..3363bbaf 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -1711,7 +1711,8 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { mh := &MetaHeadersFrame{ HeadersFrame: hf, } - var remainSize = fr.maxHeaderListSize() + var headersRemainSize = fr.maxHeaderListSize() + var trailersRemainSize = fr.maxHeaderListSize() var sawRegular bool var invalid error // pseudo header field errors @@ -1743,14 +1744,29 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { return } - size := hf.Size() - if size > remainSize { + var remainSize *uint32 + var size uint32 + if hf.Name == "trailer" { + remainSize = &trailersRemainSize + fieldCount := strings.Count(hf.Value, ",") + 1 + // Rather than actually constructing hpack.HeaderField for each + // trailer field and cumulatively adding its Size, just do the math + // manually to avoid unnecessary work. This does make it so + // whitespaces after comma are counted against the budget, but that + // should be innocuous. + size = uint32(len(hf.Value)-fieldCount+1) + uint32(fieldCount)*hpack.HeaderField{}.Size() + } else { + remainSize = &headersRemainSize + size = hf.Size() + } + if size > *remainSize { hdec.SetEmitEnabled(false) mh.Truncated = true - remainSize = 0 + headersRemainSize = 0 + trailersRemainSize = 0 return } - remainSize -= size + *remainSize -= size mh.Fields = append(mh.Fields, hf) }) @@ -1766,10 +1782,10 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (Frame, error) { // skip parsing the fragment and close the connection. // // "Too much" is either any CONTINUATION frame after we've already - // exceeded the max header list size (in which case remainSize is 0), - // or a frame whose encoded size is more than twice the remaining - // header list bytes we're willing to accept. - if int64(len(frag)) > int64(2*remainSize) { + // exceeded the max header list size (if so, both budgets are 0), or a + // frame whose encoded size is more than twice the remaining header + // list bytes we're willing to accept. + if int64(len(frag)) > 2*int64(headersRemainSize+trailersRemainSize) { if VerboseLogs { log.Printf("http2: header list too large") } diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index a7d2053b..a6cd6622 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -280,7 +280,6 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon doneServing: make(chan struct{}), clientMaxStreams: math.MaxUint32, // Section 6.5.2: "Initially, there is no limit to this value" advMaxStreams: conf.MaxConcurrentStreams, - initialStreamSendWindowSize: initialWindowSize, initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxFrameSize: initialMaxFrameSize, pingTimeout: conf.PingTimeout, @@ -320,7 +319,7 @@ func (s *Server) serveConn(c net.Conn, opts *ServeConnOpts, newf func(*serverCon // These start at the RFC-specified defaults. If there is a higher // configured value for inflow, that will be updated when we send a // WINDOW_UPDATE shortly after sending SETTINGS. - sc.flow.add(initialWindowSize) + sc.flow.init() sc.inflow.init(initialWindowSize) sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf) sc.hpackEncoder.SetMaxDynamicTableSizeLimit(conf.MaxEncoderHeaderTableSize) @@ -427,7 +426,7 @@ type serverConn struct { wroteFrameCh chan frameWriteResult // from writeFrameAsync -> serve, tickles more frame writes bodyReadCh chan bodyReadMsg // from handlers -> serve serveMsgCh chan interface{} // misc messages & code to send to / run on the serve loop - flow outflow // conn-wide (not stream-specific) outbound flow control + flow connOutflow // conn-wide (not stream-specific) outbound flow control inflow inflow // conn-wide inbound flow control tlsState *tls.ConnectionState // shared by all handlers, like net/http remoteAddrStr string @@ -441,6 +440,9 @@ type serverConn struct { sawFirstSettings bool // got the initial SETTINGS frame after the preface needToSendSettingsAck bool unackedSettings int // how many SETTINGS have we sent without ACKs? + pendingEncoderTableSize bool // peer changed SETTINGS_HEADER_TABLE_SIZE; apply to hpackEncoder before the next frame write + encoderTableSizeMin uint32 // smallest SETTINGS_HEADER_TABLE_SIZE since the last apply + encoderTableSize uint32 // latest SETTINGS_HEADER_TABLE_SIZE queuedControlFrames int // control frames in the writeSched queue clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit) advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client @@ -451,7 +453,6 @@ type serverConn struct { maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes streams map[uint32]*stream unstartedHandlers []unstartedHandler - initialStreamSendWindowSize int32 initialStreamRecvWindowSize int32 maxFrameSize int32 peerMaxHeaderListSize uint32 // zero means unknown (default) @@ -521,7 +522,8 @@ type stream struct { // immutable: sc *serverConn id uint32 - body *pipe // non-nil if expecting DATA frames + body *pipe // non-nil if expecting DATA frames + reqBody *requestBody cw closeWaiter // closed wait stream transitions to closed state ctx context.Context cancelCtx func() @@ -1172,6 +1174,16 @@ func (sc *serverConn) startFrameWrite(wr FrameWriteRequest) { sc.writingFrame = true sc.needsFrameFlush = true + if sc.pendingEncoderTableSize { + // hpackEncoder may be in use by writeFrameAsync, so SETTINGS + // changes to it are deferred until no frame is being written. + // Replaying the smallest size before the latest one keeps the + // encoder's view identical to having applied every change + // (RFC 7541, Section 4.2). + sc.pendingEncoderTableSize = false + sc.hpackEncoder.SetMaxDynamicTableSize(sc.encoderTableSizeMin) + sc.hpackEncoder.SetMaxDynamicTableSize(sc.encoderTableSize) + } if wr.write.staysWithinBuffer(sc.bw.Available()) { sc.writingFrameAsync = false err := wr.write.writeFrame(sc) @@ -1271,6 +1283,11 @@ func (sc *serverConn) scheduleFrameWrite() { } sc.inFrameScheduleLoop = true for !sc.writingFrameAsync { + if sc.flow.flowErr && (!sc.inGoAway || sc.goAwayCode == ErrCodeNo) { + sc.inGoAway = true + sc.needToSendGoAway = true + sc.goAwayCode = ErrCodeFlowControl + } if sc.needToSendGoAway { sc.needToSendGoAway = false sc.startFrameWrite(FrameWriteRequest{ @@ -1294,6 +1311,9 @@ func (sc *serverConn) scheduleFrameWrite() { sc.startFrameWrite(wr) continue } + if sc.flow.flowErr { + continue + } } if sc.needsFrameFlush { sc.startFrameWrite(FrameWriteRequest{write: flushFrameWriter{}}) @@ -1526,6 +1546,10 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error { return nil } if !st.flow.add(int32(f.Increment)) { + if st.flow.conn.flowErr { + // This is a lazily-detected connection-level flow control error. + return sc.countError("bad_flow", ConnectionError(ErrCodeFlowControl)) + } return sc.countError("bad_flow", streamError(f.StreamID, ErrCodeFlowControl)) } default: // connection-level flow control @@ -1584,10 +1608,6 @@ func (sc *serverConn) closeStream(st *stream, err error) { } } if p := st.body; p != nil { - // Return any buffered unread bytes worth of conn-level flow control. - // See golang.org/issue/16481 - sc.sendWindowUpdate(nil, p.Len()) - p.CloseWithError(err) } if e, ok := err.(StreamError); ok { @@ -1641,7 +1661,12 @@ func (sc *serverConn) processSetting(s Setting) error { } switch s.ID { case SettingHeaderTableSize: - sc.hpackEncoder.SetMaxDynamicTableSize(s.Val) + // Applied by startFrameWrite; see comment there. + if !sc.pendingEncoderTableSize || s.Val < sc.encoderTableSizeMin { + sc.encoderTableSizeMin = s.Val + } + sc.encoderTableSize = s.Val + sc.pendingEncoderTableSize = true case SettingEnablePush: sc.pushEnabled = s.Val != 0 case SettingMaxConcurrentStreams: @@ -1672,28 +1697,14 @@ func (sc *serverConn) processSetting(s Setting) error { func (sc *serverConn) processSettingInitialWindowSize(val uint32) error { sc.serveG.check() - // Note: val already validated to be within range by - // processSetting's Valid call. - - // "A SETTINGS frame can alter the initial flow control window - // size for all current streams. When the value of - // SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST - // adjust the size of all stream flow control windows that it - // maintains by the difference between the new value and the - // old value." - old := sc.initialStreamSendWindowSize - sc.initialStreamSendWindowSize = int32(val) - growth := int32(val) - old // may be negative - for _, st := range sc.streams { - if !st.flow.add(growth) { - // 6.9.2 Initial Flow Control Window Size - // "An endpoint MUST treat a change to - // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow - // control window to exceed the maximum size as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR." - return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl)) - } + if !sc.flow.changeInitialWindowSize(int64(val)) { + // 6.9.2 Initial Flow Control Window Size + // "An endpoint MUST treat a change to + // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow + // control window to exceed the maximum size as a + // connection error (Section 5.4.1) of type + // FLOW_CONTROL_ERROR." + return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl)) } return nil } @@ -1966,7 +1977,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { if st.reqTrailer != nil { st.trailer = make(http.Header) } - st.body = req.Body.(*requestBody).pipe // may be nil + st.body = st.reqBody.pipe // may be nil st.declBodyBytes = req.ContentLength handler := sc.handler.ServeHTTP @@ -1989,7 +2000,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) } - return sc.scheduleHandler(id, rw, req, handler) + return sc.scheduleHandler(st, rw, req, handler) } func (sc *serverConn) upgradeRequest(req *http.Request) { @@ -2102,7 +2113,6 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState, priority } st.cw.Init() st.flow.conn = &sc.flow // link to conn-level counter - st.flow.add(sc.initialStreamSendWindowSize) st.inflow.init(sc.initialStreamRecvWindowSize) if sc.hs.WriteTimeout > 0 { st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout) @@ -2184,7 +2194,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res } else { req.ContentLength = -1 } - req.Body.(*requestBody).pipe = &pipe{ + st.reqBody.pipe = &pipe{ b: &dataBuffer{expected: req.ContentLength}, } } @@ -2204,7 +2214,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol)) } - body := &requestBody{ + st.reqBody = &requestBody{ conn: sc, stream: st, needsContinue: res.NeedsContinue, @@ -2220,7 +2230,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.Server ProtoMinor: 0, TLS: tlsState, Host: rp.Authority, - Body: body, + Body: st.reqBody, Trailer: res.Trailer, }).WithContext(st.ctx) rw := sc.newResponseWriter(st, req) @@ -2244,11 +2254,12 @@ type unstartedHandler struct { rw *responseWriter req *http.Request handler func(http.ResponseWriter, *http.Request) + body *pipe } // scheduleHandler starts a handler goroutine, // or schedules one to start as soon as an existing handler finishes. -func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { +func (sc *serverConn) scheduleHandler(st *stream, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { sc.serveG.check() maxHandlers := sc.advMaxStreams if sc.curHandlers < maxHandlers { @@ -2260,10 +2271,11 @@ func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req * return sc.countError("too_many_early_resets", ConnectionError(ErrCodeEnhanceYourCalm)) } sc.unstartedHandlers = append(sc.unstartedHandlers, unstartedHandler{ - streamID: streamID, + streamID: st.id, rw: rw, req: req, handler: handler, + body: st.body, }) return nil } @@ -2277,6 +2289,10 @@ func (sc *serverConn) handlerDone() { u := sc.unstartedHandlers[i] if sc.streams[u.streamID] == nil { // This stream was reset before its goroutine had a chance to start. + if u.body != nil { + u.body.BreakWithError(errClosedBody) + sc.sendWindowUpdate(nil, u.body.Len()) + } continue } if sc.curHandlers >= maxHandlers { @@ -2298,6 +2314,12 @@ func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler didPanic := true defer func() { rw.rws.stream.cancelCtx() + if b := rw.rws.stream.reqBody; b != nil { + // Closing the body refunds flow control credit for any unconsumed data. + // (reqBody is nil for Upgrade: h2c requests, but those do not use flow + // control for the request body.) + b.Close() + } if req.MultipartForm != nil { req.MultipartForm.RemoveAll() } @@ -2396,7 +2418,7 @@ func (sc *serverConn) noteBodyReadFromHandler(st *stream, n int, err error) { func (sc *serverConn) noteBodyRead(st *stream, n int) { sc.serveG.check() sc.sendWindowUpdate(nil, n) // conn-level - if st.state != stateHalfClosedRemote && st.state != stateClosed { + if st != nil && st.state != stateHalfClosedRemote && st.state != stateClosed { // Don't send this WINDOW_UPDATE if the stream is closed // remotely. sc.sendWindowUpdate(st, n) @@ -2444,6 +2466,9 @@ func (b *requestBody) Close() error { b.closeOnce.Do(func() { if b.pipe != nil { b.pipe.BreakWithError(errClosedBody) + if unread := b.pipe.Len(); unread > 0 { + b.conn.noteBodyReadFromHandler(nil, unread, errClosedBody) + } } }) return nil @@ -2461,9 +2486,6 @@ func (b *requestBody) Read(p []byte) (n int, err error) { if err == io.EOF { b.sawEOF = true } - if b.conn == nil { - return - } b.conn.noteBodyReadFromHandler(b.stream, n, err) return } diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 088eb9be..d894c601 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -27,6 +27,7 @@ import ( "net/http" "net/http/httptrace" "net/textproto" + "slices" "strconv" "sync" "sync/atomic" @@ -203,7 +204,7 @@ type ClientConn struct { mu sync.Mutex // guards following cond *sync.Cond // hold mu; broadcast on flow/closed changes - flow outflow // our conn-level flow control quota (cs.outflow is per stream) + flow connOutflow // our conn-level flow control quota (cs.outflow is per stream) inflow inflow // peer's conn-level flow control doNotReuse bool // whether conn is marked to not be reused for any future requests closing bool @@ -227,7 +228,6 @@ type ClientConn struct { maxConcurrentStreams uint32 peerMaxHeaderListSize uint64 peerMaxHeaderTableSize uint32 - initialWindowSize uint32 initialStreamRecvWindowSize int32 readIdleTimeout time.Duration pingTimeout time.Duration @@ -472,7 +472,6 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook readerDone: make(chan struct{}), nextStreamID: 1, maxFrameSize: 16 << 10, // spec default - initialWindowSize: 65535, // spec default initialStreamRecvWindowSize: conf.MaxUploadBufferPerStream, maxConcurrentStreams: initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings. strictMaxConcurrentStreams: conf.StrictMaxConcurrentRequests, @@ -497,7 +496,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook } cc.cond = sync.NewCond(&cc.mu) - cc.flow.add(int32(initialWindowSize)) + cc.flow.init() // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. @@ -876,6 +875,33 @@ func (cc *ClientConn) sendGoAway() error { return nil } +func (cc *ClientConn) goAwayAndClose(code ErrCode) { + cc.mu.Lock() + closed := cc.closed + cc.closing = true + cc.closed = true + cc.mu.Unlock() + if closed { + return + } + if f := cc.fr.countError; f != nil { + f(fmt.Sprintf("conn_close_error_%s", code.stringToken())) + } + done := make(chan struct{}) + go func() { + defer close(done) + cc.wmu.Lock() + cc.fr.WriteGoAway(0, code, nil) + cc.bw.Flush() + cc.wmu.Unlock() + }() + select { + case <-done: + case <-time.After(250 * time.Millisecond): + } + cc.closeForError(fmt.Errorf("http2: closing connection with %v", code)) +} + // closes the client connection immediately. In-flight requests are interrupted. // err is sent to streams. func (cc *ClientConn) closeForError(err error) { @@ -1319,7 +1345,9 @@ func (cs *clientStream) cleanupWriteRequest(err error) { } if err != nil { cs.abortStream(err) // possibly redundant, but harmless - if cs.sentHeaders { + if ce, ok := err.(ConnectionError); ok { + cc.goAwayAndClose(ErrCode(ce)) + } else if cs.sentHeaders { if se, ok := err.(StreamError); ok { if se.Cause != errFromPeer { cc.writeStreamReset(cs.ID, se.Code, false, err) @@ -1648,8 +1676,12 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) return 0, errRequestCanceled default: } - if a := cs.flow.available(); a > 0 { - take := a + avail, ok := cs.flow.available() + if !ok { + return 0, ConnectionError(ErrCodeFlowControl) + } + if avail > 0 { + take := avail if int(take) > maxBytes { take = int32(maxBytes) // can't truncate int; take is int32 @@ -1710,8 +1742,7 @@ type resAndError struct { // requires cc.mu be held. func (cc *ClientConn) addStreamLocked(cs *clientStream) { - cs.flow.add(int32(cc.initialWindowSize)) - cs.flow.setConnFlow(&cc.flow) + cs.flow.conn = &cc.flow cs.inflow.init(cc.initialStreamRecvWindowSize) cs.ID = cc.nextStreamID cc.nextStreamID += 2 @@ -2095,18 +2126,42 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra return nil, nil } + // Delete various headers that might mess up framing for HTTP/1. This is + // not a problem for HTTP/2, but someone might use HTTP/2 transport as a + // reverse proxy which forwards the response to an HTTP/1 client. Our + // HTTP/1 client transport will properly reject improper headers such as + // multiple conflicting Content-Length headers, but other implementations + // might not. + // TODO: just reject such responses? We deleted them for compatibility + // since this was done in a security fix (go.dev/issue/81115). However, + // rejecting them seems entirely reasonable and relatively safe. + + // Connection-specific header fields must not appear in an HTTP/2 message, + // and any message containing them is malformed. RFC 9113, Section 8.2.2. + for _, k := range connHeaders { + delete(res.Header, k) + } res.ContentLength = -1 - if clens := res.Header["Content-Length"]; len(clens) == 1 { - if cl, err := strconv.ParseUint(clens[0], 10, 63); err == nil { - res.ContentLength = int64(cl) + if clens, ok := res.Header["Content-Length"]; ok { + // Repeated Content-Length values may be collapsed into one only if + // they are identical per RFC 9110 Section 8.6. + // No need to trim whitespace, HTTP/2 header values must not have + // extraneous whitespace per RFC 9113 Section 8.2.1. + // Non-canonical headers are already rejected by our framer at this + // point. + conflicting := slices.ContainsFunc(clens[1:], func(clen string) bool { + return clen != clens[0] + }) + cl, err := strconv.ParseUint(clens[0], 10, 63) + if conflicting || err != nil { + delete(res.Header, "Content-Length") } else { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. + res.Header["Content-Length"] = clens[:1] + res.ContentLength = int64(cl) } - } else if len(clens) > 1 { - // TODO: care? unlike http/1, it won't mess up our framing, so it's - // more safe smuggling-wise to ignore. - } else if f.StreamEnded() && !cs.isHead { + } + + if res.ContentLength < 0 && f.StreamEnded() && !cs.isHead { res.ContentLength = 0 } @@ -2417,6 +2472,10 @@ const ( func (rl *clientConnReadLoop) streamByID(id uint32, headerOrData bool) *clientStream { rl.cc.mu.Lock() defer rl.cc.mu.Unlock() + return rl.streamByIDLocked(id, headerOrData) +} + +func (rl *clientConnReadLoop) streamByIDLocked(id uint32, headerOrData bool) *clientStream { if headerOrData { // Work around an unfortunate gRPC behavior. // See comment on ClientConn.rstStreamPingsBlocked for details. @@ -2499,24 +2558,10 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { case SettingMaxHeaderListSize: cc.peerMaxHeaderListSize = uint64(s.Val) case SettingInitialWindowSize: - // Values above the maximum flow-control - // window size of 2^31-1 MUST be treated as a - // connection error (Section 5.4.1) of type - // FLOW_CONTROL_ERROR. - if s.Val > math.MaxInt32 { + if !cc.flow.changeInitialWindowSize(int64(s.Val)) { return ConnectionError(ErrCodeFlowControl) } - - // Adjust flow control of currently-open - // frames by the difference of the old initial - // window size and this one. - delta := int32(s.Val) - int32(cc.initialWindowSize) - for _, cs := range cc.streams { - cs.flow.add(delta) - } cc.cond.Broadcast() - - cc.initialWindowSize = s.Val case SettingHeaderTableSize: cc.henc.SetMaxDynamicTableSize(s.Val) cc.peerMaxHeaderTableSize = s.Val @@ -2558,29 +2603,30 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error { func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error { cc := rl.cc - cs := rl.streamByID(f.StreamID, notHeaderOrDataFrame) - if f.StreamID != 0 && cs == nil { - return nil - } - cc.mu.Lock() defer cc.mu.Unlock() - - fl := &cc.flow - if cs != nil { - fl = &cs.flow - } - if !fl.add(int32(f.Increment)) { - // For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR - if cs != nil { + if f.StreamID == 0 { + if !cc.flow.add(int32(f.Increment)) { + return ConnectionError(ErrCodeFlowControl) + } + } else { + cs := rl.streamByIDLocked(f.StreamID, notHeaderOrDataFrame) + if cs == nil { + return nil + } + if !cs.flow.add(int32(f.Increment)) { + if cs.flow.conn.flowErr { + // This is a lazily-detected connection-level flow control error. + return ConnectionError(ErrCodeFlowControl) + } + // For stream, the sender sends RST_STREAM with + // an error code of FLOW_CONTROL_ERROR. rl.endStreamErrorLocked(cs, StreamError{ StreamID: f.StreamID, Code: ErrCodeFlowControl, }) return nil } - - return ConnectionError(ErrCodeFlowControl) } cc.cond.Broadcast() return nil diff --git a/vendor/golang.org/x/net/http2/transport_wrap.go b/vendor/golang.org/x/net/http2/transport_wrap.go index 34dfb3cc..eeaa95f0 100644 --- a/vendor/golang.org/x/net/http2/transport_wrap.go +++ b/vendor/golang.org/x/net/http2/transport_wrap.go @@ -119,33 +119,27 @@ type http2TransportContextKey struct{} // DialFromContext dials a new connection using the http2.Transport's DialTLS/DialTLSContext. func (t transportConfig) DialFromContext(ctx context.Context, network, address string) (net.Conn, error) { - if ctx.Value(http2TransportContextKey{}) == nil { + dial, _ := ctx.Value(http2TransportContextKey{}).(*transportRoundTripState) + if dial == nil { // We're being called from a RoundTrip that did not start with an http2.Transport. // Use the http.Transport's dialer. return nil, errors.ErrUnsupported } - - tlsConf := t.t.TLSClientConfig - if tlsConf == nil { - tlsConf = &tls.Config{} - } else { - tlsConf = tlsConf.Clone() - } - if !slices.Contains(tlsConf.NextProtos, "h2") { - tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...) - } - if tlsConf.ServerName == "" { - host, _, err := net.SplitHostPort(address) - if err == nil { - tlsConf.ServerName = host - } - } - return t.t.dialTLS(ctx, network, address, tlsConf) + return t.t.dialForRoundTrip(ctx, dial, network, address) } type transportInternal struct { initOnce sync.Once lazyt1 *http.Transport + + dialMu sync.Mutex + dials map[string]*transportDialState +} + +type transportDialState struct { + dialc chan struct{} // close when dial returns + dialErr error // dial result, set before dialc is closed + rtdonec chan struct{} // closed when RoundTrip initiating the dial returns } func (t *Transport) init() *http.Transport { @@ -167,6 +161,52 @@ func (t *Transport) configure(t1 *http.Transport) { } } +// transportRoundTripState is the state of the dial for an http2.Transport.RoundTrip. +type transportRoundTripState struct { + mu sync.Mutex + rtdone bool // set when RoundTrip returns + rtdonec chan struct{} // closed when RoundTrip returns + gotconnc chan struct{} // closed when GotConn hook is called +} + +// startDial is called when a dial starts. +// +// It returns done=true if the RoundTrip has already returned, +// in which case we should skip dialing. +func (dial *transportRoundTripState) startDial() (gotconnc chan struct{}, done bool) { + dial.mu.Lock() + defer dial.mu.Unlock() + if dial.rtdone { + return nil, true + } + if dial.rtdonec == nil { + dial.rtdonec = make(chan struct{}) + } + dial.gotconnc = make(chan struct{}) + return dial.gotconnc, false +} + +// gotConn is called when RoundTrip gets a connection. +// This may happen multiple times per RoundTrip, if a request is retried. +func (dial *transportRoundTripState) gotConn() { + dial.mu.Lock() + defer dial.mu.Unlock() + if dial.gotconnc != nil { + close(dial.gotconnc) + dial.gotconnc = nil + } +} + +// roundTripDone is called when RoundTrip returns. +func (dial *transportRoundTripState) roundTripDone() { + dial.mu.Lock() + defer dial.mu.Unlock() + dial.rtdone = true + if dial.rtdonec != nil { + close(dial.rtdonec) + } +} + func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { t1 := t.init() @@ -186,12 +226,146 @@ func (t *Transport) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res // Both http.Transport and http2.Transport allow the user to provide a custom // dial function, and historically you only get the dial function from the // Transport you're calling RoundTrip on. - ctx := context.WithValue(req.Context(), http2TransportContextKey{}, t) + // + // In addition, http2.Transport coalesces dials, which http.Transport historically + // has not. + dial := &transportRoundTripState{} + defer dial.roundTripDone() + ctx := context.WithValue(req.Context(), http2TransportContextKey{}, dial) + ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + GotConn: func(httptrace.GotConnInfo) { + // Tell dialForRoundTrip that we have received a connection. + dial.gotConn() + }, + }) req = req.WithContext(ctx) - return t1.RoundTrip(req) } +var errCoalescedDialAbandoned = errors.New("http2: abandoned coalesced dial") + +const coalescedDialRetryTimeout = 50 * time.Millisecond + +// dialForRoundTrip is called (indirectly) by net/http when dialing a new connection +// for a request call which originated as an http2.Transport.RoundTrip call. +// +// It uses the http2.Transport's Dial hooks and coalesces dials. +func (t *Transport) dialForRoundTrip(ctx context.Context, dial *transportRoundTripState, network, address string) (net.Conn, error) { + gotconnc, rtdone := dial.startDial() + if rtdone { + // RoundTrip returned, no need for this dial to proceed. + return nil, errCoalescedDialAbandoned + } + + var state *transportDialState + for { + // The first dial to an address registers itself with t.dials. + // When the dial finishes, it records the outcome and removes itself from t.dials + // so future dials will start a new round of coalescing. + // + // Subsequent dials observe that a dial is in progress, and skip dialing. + t.dialMu.Lock() + if t.dials == nil { + t.dials = make(map[string]*transportDialState) + } + leader := t.dials[address] + if leader == nil { + // No entry in t.dials to coalesce with. Add ourselves as leader. + state = &transportDialState{ + dialc: make(chan struct{}), + rtdonec: dial.rtdonec, + } + t.dials[address] = state + } + t.dialMu.Unlock() + if leader == nil { + // We are the leader, so we should dial for real. + break + } + + // Coalesce with a previous dial. + var ( + done = false + leaderdialc = leader.dialc + leaderrtdonec = leader.rtdonec + timerc <-chan time.Time + ) + for !done { + if leaderdialc == nil && leaderrtdonec == nil && timerc == nil { + // The leader finished dialing, and its RoundTrip finished. + // + // There are several possibilities, which reduce to: + // - The RoundTrip we are dialing for is about to receive + // a connection (possibly the one the leader just dialed), + // but we haven't observed it yet. + // - There's something wrong with the leader's connection, + // such as a TLS handshake failure. + // + // We have no good way to distinguish between these cases. + // + // Wait a short time for the leader's connection (or some + // other usable connection) to be delivered to our RoundTrip. + // If it is not, start a dial of our own. + timerc = time.After(coalescedDialRetryTimeout) + } + select { + case <-leaderdialc: + // The leader's dial completed. + if leader.dialErr != nil { + return nil, leader.dialErr + } + leaderdialc = nil + case <-leaderrtdonec: + // The leader's RoundTrip completed. + leaderrtdonec = nil + case <-timerc: + // The leader's dial and RoundTrip completed, and some time + // has passed. Assume we're not getting a connection and + // restart the dial process. + done = true + case <-dial.rtdonec: + // Our RoundTrip returned. + return nil, errCoalescedDialAbandoned + case <-gotconnc: + // Our RoundTrip got a connection. + return nil, errCoalescedDialAbandoned + case <-ctx.Done(): + // net/http abandoned the dial. + return nil, ctx.Err() + } + } + } + + // Dial for real. + tlsConf := t.TLSClientConfig + if tlsConf == nil { + tlsConf = &tls.Config{} + } else { + tlsConf = tlsConf.Clone() + } + if !slices.Contains(tlsConf.NextProtos, "h2") { + tlsConf.NextProtos = append([]string{"h2"}, tlsConf.NextProtos...) + } + if tlsConf.ServerName == "" { + host, _, err := net.SplitHostPort(address) + if err == nil { + tlsConf.ServerName = host + } + } + nc, err := t.dialTLS(ctx, network, address, tlsConf) + + // Remove this dial from t.dials, so future dial attempts will not coalesce with it. + t.dialMu.Lock() + delete(t.dials, address) + t.dialMu.Unlock() + + // Notify any dial coalesced with this one that we are done. + state.dialErr = err + close(state.dialc) + + return nc, err +} + func (t *Transport) closeIdleConnections() { t1 := t.init() t1.CloseIdleConnections() diff --git a/vendor/golang.org/x/net/http2/writesched.go b/vendor/golang.org/x/net/http2/writesched.go index 36ad5e32..f1903a8c 100644 --- a/vendor/golang.org/x/net/http2/writesched.go +++ b/vendor/golang.org/x/net/http2/writesched.go @@ -79,10 +79,11 @@ func (wr FrameWriteRequest) Consume(n int32) (FrameWriteRequest, FrameWriteReque } // Might need to split after applying limits. - allowed := wr.stream.flow.available() - if n < allowed { - allowed = n + avail, ok := wr.stream.flow.available() + if !ok { + return empty, empty, 0 } + allowed := min(n, avail) if wr.stream.sc.maxFrameSize < allowed { allowed = wr.stream.sc.maxFrameSize } diff --git a/vendor/golang.org/x/net/internal/httpcommon/gzip.go b/vendor/golang.org/x/net/internal/httpcommon/gzip.go new file mode 100644 index 00000000..819dd650 --- /dev/null +++ b/vendor/golang.org/x/net/internal/httpcommon/gzip.go @@ -0,0 +1,132 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package httpcommon + +import ( + "compress/flate" + "compress/gzip" + "errors" + "io" + "io/fs" + "sync" +) + +var errConcurrentRead = errors.New("http: concurrent read on response body") + +// incomparable is a zero-width, non-comparable type. Adding it to a struct +// makes that struct also non-comparable, and generally doesn't add +// any size (as long as it's first). +type incomparable [0]func() + +// GzipReader wraps a response body so it can lazily +// get gzip.Reader from the pool on the first call to Read. +// After Close is called it puts gzip.Reader to the pool immediately +// if there is no Read in progress or later when Read completes. +type GzipReader struct { + _ incomparable + Body io.ReadCloser // underlying Response.Body + mu sync.Mutex // guards zr and zerr + zr *gzip.Reader // stores gzip reader from the pool between reads + zerr error // sticky gzip reader init error or sentinel value to detect concurrent read and read after close +} + +type eofReader struct{} + +func (eofReader) Read([]byte) (int, error) { return 0, io.EOF } +func (eofReader) ReadByte() (byte, error) { return 0, io.EOF } + +var gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }} + +// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r. +func gzipPoolGet(r io.Reader) (*gzip.Reader, error) { + zr := gzipPool.Get().(*gzip.Reader) + if err := zr.Reset(r); err != nil { + gzipPoolPut(zr) + return nil, err + } + return zr, nil +} + +// gzipPoolPut puts a gzip.Reader back into the pool. +func gzipPoolPut(zr *gzip.Reader) { + // Reset will allocate bufio.Reader if we pass it anything + // other than a flate.Reader, so ensure that it's getting one. + var r flate.Reader = eofReader{} + zr.Reset(r) + gzipPool.Put(zr) +} + +// acquire returns a gzip.Reader for reading response body. +// The reader must be released after use. +func (gz *GzipReader) acquire() (*gzip.Reader, error) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr != nil { + return nil, gz.zerr + } + if gz.zr == nil { + // gzipPoolGet might block indefinitely since it reads the gzip header. + // Therefore, drop mu temporarily when using gzipPoolGet. + // We set zerr to errConcurrentRead to prevent concurrent read + // even when mu is temporarily dropped. + gz.zerr = errConcurrentRead + gz.mu.Unlock() + zr, err := gzipPoolGet(gz.Body) + gz.mu.Lock() + // Guard against Close being called while gzipPoolGet is running. + if gz.zerr != errConcurrentRead { + if zr != nil { + gzipPoolPut(zr) + } + return nil, gz.zerr + } + gz.zr, gz.zerr = zr, err + if gz.zerr != nil { + return nil, gz.zerr + } + } + ret := gz.zr + gz.zr, gz.zerr = nil, errConcurrentRead + return ret, nil +} + +// release returns the gzip.Reader to the pool if Close was called during Read. +func (gz *GzipReader) release(zr *gzip.Reader) { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == errConcurrentRead { + gz.zr, gz.zerr = zr, nil + } else { // fs.ErrClosed + gzipPoolPut(zr) + } +} + +// close returns the gzip.Reader to the pool immediately or +// signals release to do so after Read completes. +func (gz *GzipReader) close() { + gz.mu.Lock() + defer gz.mu.Unlock() + if gz.zerr == nil && gz.zr != nil { + gzipPoolPut(gz.zr) + gz.zr = nil + } + gz.zerr = fs.ErrClosed +} + +func (gz *GzipReader) Read(p []byte) (n int, err error) { + zr, err := gz.acquire() + if err != nil { + return 0, err + } + defer gz.release(zr) + + return zr.Read(p) +} + +func (gz *GzipReader) Close() error { + gz.close() + + return gz.Body.Close() +} diff --git a/vendor/google.golang.org/grpc/CONTRIBUTING.md b/vendor/google.golang.org/grpc/CONTRIBUTING.md index 2079de7b..bd80b1f9 100644 --- a/vendor/google.golang.org/grpc/CONTRIBUTING.md +++ b/vendor/google.golang.org/grpc/CONTRIBUTING.md @@ -23,6 +23,29 @@ wanted"](https://github.com/grpc/grpc-go/issues?q=sort%3Aupdated-desc%20is%3Aiss are especially nice for first-time contributors, as they should be well-defined problems that already have agreed-upon solutions. +## Generative AI Policy + +AI tools have the ability to produce more code than is possible for the gRPC +team to read, understand, review, and accept into the repository. For this +reason, we request that all contributions adhere to the following rules: + +1. **No AI-Generated Interactions:** All communication in the repo must be + authored by a human. _Exception: AIs may be used for directed writing + assistance or translation._ Absolutely no automated agents are allowed to + directly publish to GitHub. + +2. **Author Ownership and Accountability:** Code contributions are expected to + be fully owned and understood by the human contributor. If the code was + produced by generative AI, the author is expected to have reviewed and + understood it in its entirety before submitting it for review. This includes + all content: production code, tests, examples, tools, etc. + +In addition to the above requirements, any AI-assisted contributions must also +comply with the [Linux Foundation Generative AI +Policy](https://www.linuxfoundation.org/legal/generative-ai). This includes +confirming that all contributions are legally allowed to be contributed to the +gRPC project under the applicable license terms. + ## Code Style We follow [Google's published Go style diff --git a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go index 6c249fda..d22d1c3f 100644 --- a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go +++ b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.11 +// protoc-gen-go v1.36.12 // protoc v5.27.1 // source: grpc/binlog/v1/binarylog.proto diff --git a/vendor/google.golang.org/grpc/call.go b/vendor/google.golang.org/grpc/call.go index 2079bf4e..788c89c1 100644 --- a/vendor/google.golang.org/grpc/call.go +++ b/vendor/google.golang.org/grpc/call.go @@ -20,10 +20,6 @@ package grpc import ( "context" - "io" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) // Invoke sends the RPC request on the wire and returns after response is @@ -71,27 +67,8 @@ func invoke(ctx context.Context, method string, req, reply any, cc *ClientConn, if err != nil { return err } - // In case of nil or io.EOF error, call RecvMsg. - if err := cs.SendMsg(req); err != nil && err != io.EOF { - return err - } - // CloseSend is needed because in some scenarios (e.g., xDS), the same - // interceptors are used to process both unary and streaming RPCs. Calling - // CloseSend signals to those interceptors that no more messages are on the - // way. - if err := cs.CloseSend(); err != nil && err != io.EOF { + if err := cs.SendMsg(req); err != nil { return err } - if err := cs.RecvMsg(reply); err != nil { - return err - } - // Call RecvMsg again to get the trailers. - err = cs.RecvMsg(reply) - if err == io.EOF { - return nil - } - if err == nil { - return status.Error(codes.Internal, "cardinality violation: expected for non server-streaming RPCs, but received another message") - } - return err + return cs.RecvMsg(reply) } diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index f5ce0719..7a25d251 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -553,7 +553,6 @@ func chainStreamClientInterceptors(cc *ClientConn) { if cc.dopts.streamInt != nil { interceptors = append([]StreamClientInterceptor{cc.dopts.streamInt}, interceptors...) } - interceptors = append(interceptors, defaultStreamInterceptor) var chainedInt StreamClientInterceptor if len(interceptors) == 0 { chainedInt = nil @@ -1212,6 +1211,12 @@ func (cc *ClientConn) Close() error { cc.mu.Unlock() cc.resolverWrapper.close() + // Swap in a default ConfigSelector so the resolver-provided one (which may + // transitively retain resolver-owned state such as parsed service configs + // and cluster maps) is released once in-flight RPCs finish selecting on + // it. Done after the resolver has closed so no further UpdateState calls + // race with this swap. + cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil}) // The order of closing matters here since the balancer wrapper assumes the // picker is closed before it is closed. cc.pickerWrapper.close() diff --git a/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go b/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go index 96693289..ca0e586d 100644 --- a/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go +++ b/vendor/google.golang.org/grpc/internal/binarylog/method_logger.go @@ -395,7 +395,7 @@ func (c *Cancel) toProto() *binlogpb.GrpcLogEntry { // omitted. func metadataKeyOmit(key string) bool { switch key { - case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te": + case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te", "authorization": return true case "grpc-trace-bin": // grpc-trace-bin is special because it's visible to users. return false diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index dc6fc0c4..2f4ae2fa 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -144,14 +144,14 @@ var ( // 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. + // must be between 1 and 100,000, and is set to 10,000 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 released. - ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 100, 1, 10000) + // TODO: Remove this env var once v1.86.0 is released. + ControlBufferThrottleLimit = uint64FromEnv("GRPC_GO_EXPERIMENTAL_CONTROL_BUFFER_THROTTLE_LIMIT", 10000, 1, 100000) // ALTSMaxFrameSize is the maximum frame size for ALTS in bytes. // This can be overridden by setting the environment variable diff --git a/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go b/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go index 070680ed..4718576e 100644 --- a/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go +++ b/vendor/google.golang.org/grpc/internal/transport/bdp_estimator.go @@ -47,10 +47,9 @@ const ( var bdpPing = &ping{data: [8]byte{2, 4, 16, 16, 9, 14, 7, 7}} type bdpEstimator struct { + mu sync.Mutex // sentAt is the time when the ping was sent. sentAt time.Time - - mu sync.Mutex // bdp is the current bdp estimate. bdp uint32 // sample is the number of bytes received in one measurement cycle. @@ -75,7 +74,9 @@ func (b *bdpEstimator) timesnap(d [8]byte) { if bdpPing.data != d { return } + b.mu.Lock() b.sentAt = time.Now() + b.mu.Unlock() } // add adds bytes to the current sample for calculating bdp. diff --git a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go index 89c938fd..e33ac3a5 100644 --- a/vendor/google.golang.org/grpc/internal/transport/controlbuf.go +++ b/vendor/google.golang.org/grpc/internal/transport/controlbuf.go @@ -26,6 +26,7 @@ import ( "runtime" "sync" "sync/atomic" + "time" "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" @@ -97,22 +98,35 @@ 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) +// maxQueuedTransportResponseFrames is the maximum number of "transport +// response" frames that we will buffer before preventing new reads from +// occurring on the transport. See cbItem.isTransportResponseFrame for the +// items that count towards this limit. +var maxQueuedTransportResponseFrames = int(envconfig.ControlBufferThrottleLimit) type cbItem interface { - isThrottled() bool + // isTransportResponseFrame reports whether the item counts against the + // maxQueuedTransportResponseFrames limit, when queued in the + // controlBuffer of a transport on the given side. + // + // Items that the peer can make the transport queue in unbounded + // numbers, for example by sending PING or SETTINGS frames, must be + // counted, so that the transport stops reading from a peer that sends + // such frames without reading what the transport writes. Items whose + // number grows with the local application's load (for example, one + // per stream it creates) must not be counted. Otherwise, under high + // concurrency, both peers could stop reading at the same time and + // deadlock (see https://github.com/grpc/grpc-go/issues/9330). + isTransportResponseFrame(s side) 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{} +// nonTransportResponseFrame represents items in the controlBuffer that are not +// queued in response to peer control/reset frames (for example, +// locally-initiated or flow-controlled items). These items are not counted +// against the maxQueuedTransportResponseFrames limit. +type nonTransportResponseFrame struct{} -func (throttledItem) isThrottled() bool { return true } +func (nonTransportResponseFrame) isTransportResponseFrame(side) bool { return false } // The following defines various control items which could flow through // the control buffer of transport. They represent different aspects of @@ -120,12 +134,13 @@ func (throttledItem) isThrottled() bool { return true } // registerStream is used to register an incoming stream with loopy writer. type registerStream struct { - throttledItem + nonTransportResponseFrame streamID uint32 wq *writeQuota } type clientHeaders struct { + nonTransportResponseFrame streamID uint32 hf []hpack.HeaderField initStream func(uint32) error @@ -134,9 +149,8 @@ type clientHeaders struct { onOrphaned func(error) } -func (*clientHeaders) isThrottled() bool { return false } - type serverHeaders struct { + nonTransportResponseFrame streamID uint32 hf []hpack.HeaderField endStream bool @@ -144,24 +158,37 @@ type serverHeaders struct { cleanup *cleanupStream } -func (h *serverHeaders) isThrottled() bool { return false } - type cleanupStream struct { - throttledItem streamID uint32 rst bool rstCode http2.ErrCode onWrite func() } +// isTransportResponseFrame returns true on the server since cleanupStream +// either results in a RST_STREAM frame being sent on the wire or in +// non-trivial work being performed when under a Rapid Reset attack. +// +// It returns false on the client, where a cleanupStream is queued at most once +// per stream, and the number of streams is controlled by the client and not by +// the server. Counting them on the client makes the client stop reading under +// high concurrency, which deadlocks with a server that has also stopped +// reading (see https://github.com/grpc/grpc-go/issues/9330). +func (*cleanupStream) isTransportResponseFrame(s side) bool { return s == serverSide } + type earlyAbortStream struct { - throttledItem streamID uint32 rst bool hf []hpack.HeaderField // Pre-built header fields } +// isTransportResponseFrame returns true on the server since earlyAbortStream +// results in a HEADERS frame being sent on the wire, and possibly a RST_STREAM +// frame. It is never queued on the client. +func (*earlyAbortStream) isTransportResponseFrame(s side) bool { return s == serverSide } + type dataFrame struct { + nonTransportResponseFrame streamID uint32 endStream bool h []byte @@ -172,36 +199,40 @@ type dataFrame struct { onEachWrite func() } -func (*dataFrame) isThrottled() bool { return false } - type incomingWindowUpdate struct { - throttledItem streamID uint32 increment uint32 } +// isTransportResponseFrame returns true to protect against a WINDOW_UPDATE +// flood attack from the peer. +func (*incomingWindowUpdate) isTransportResponseFrame(side) bool { return true } + type outgoingWindowUpdate struct { - throttledItem + nonTransportResponseFrame streamID uint32 increment uint32 } type incomingSettings struct { - throttledItem ss []http2.Setting } +// isTransportResponseFrame returns true since incomingSettings results in a +// SETTINGS ACK frame being sent on the wire. +func (*incomingSettings) isTransportResponseFrame(side) bool { return true } + type outgoingSettings struct { - throttledItem + nonTransportResponseFrame ss []http2.Setting } type incomingGoAway struct { - throttledItem + nonTransportResponseFrame } type goAway struct { - throttledItem + nonTransportResponseFrame code http2.ErrCode debugData []byte headsUp bool @@ -209,13 +240,23 @@ type goAway struct { } type ping struct { - throttledItem ack bool data [8]byte } +// isTransportResponseFrame returns true for all pings, and not only for ping +// acks sent in response to pings from the peer. This is because a BDP ping is +// queued in response to a DATA frame (of any size) from the peer, whenever +// there is no BDP ping outstanding. And since the BDP ping's payload is fixed, +// the peer can make it look like the BDP ping was acked at any time, by +// sending a ping ack with that payload. A peer alternating between empty DATA +// frames and such ping acks would otherwise be able to queue an unbounded +// number of BDP pings. Pings initiated locally (keepalive and BDP) are rare +// enough to not cause throttling of well-behaved peers. +func (*ping) isTransportResponseFrame(side) bool { return true } + type outFlowControlSizeRequest struct { - throttledItem + nonTransportResponseFrame resp chan uint32 } @@ -225,7 +266,7 @@ type outFlowControlSizeRequest struct { // can inspect settled accounting values (such as bytesOutStanding) after loopyWriter // has run to completion without any data races. type outStreamRequestForTesting struct { - throttledItem + nonTransportResponseFrame streamID uint32 resp chan *outStream } @@ -235,7 +276,7 @@ type outStreamRequestForTesting struct { // (by the client or server). The transport itself will close after the reader // encounters the EOF caused by the connection closure. type closeConnection struct { - throttledItem + nonTransportResponseFrame } type outStreamState int @@ -316,8 +357,9 @@ func (l *outStreamList) dequeue() *outStream { // shouldn't be confused with an HTTP2 frame, although some of the control // frames like dataFrame and headerFrame do go out on wire as HTTP2 frames. type controlBuffer struct { - wakeupCh chan struct{} // Unblocks readers waiting for something to read. + wakeupCh chan struct{} // Unblocks readers waiting for new items. done <-chan struct{} // Closed when the transport is done. + side side // Side of the transport owning this buffer. // Mutex guards all the fields below, except trfChan which can be read // atomically without holding mu. @@ -326,26 +368,30 @@ type controlBuffer struct { closed bool // True when the controlbuf is finished. list *itemList // List of queued control frames. - // transportResponseFrames counts the number of queued items that represent - // the response of an action initiated by the peer. trfChan is created - // when transportResponseFrames >= maxQueuedTransportResponseFrames and is - // closed and nilled when transportResponseFrames drops below the - // threshold. Both fields are protected by mu. + // transportResponseFrames counts the number of queued items that + // represent the response of an action initiated by the peer (as + // determined by cbItem.isTransportResponseFrame for this side). + // trfChan is created when transportResponseFrames >= + // maxQueuedTransportResponseFrames and is closed and nilled when + // transportResponseFrames drops below the threshold. Both fields are + // protected by mu. transportResponseFrames int trfChan atomic.Pointer[chan struct{}] } -func newControlBuffer(done <-chan struct{}) *controlBuffer { +func newControlBuffer(done <-chan struct{}, s side) *controlBuffer { return &controlBuffer{ wakeupCh: make(chan struct{}, 1), list: &itemList{}, done: done, + side: s, } } -// throttle blocks if there are too many frames in the control buf that -// represent the response of an action initiated by the peer, like -// incomingSettings cleanupStreams etc. +// throttle blocks if there are too many items in the control buffer that count +// towards the maxQueuedTransportResponseFrames limit (see +// cbItem.isTransportResponseFrame), until their number drops below the limit, +// the control buffer is finished, or the transport is done. func (c *controlBuffer) throttle() { if ch := c.trfChan.Load(); ch != nil { select { @@ -355,6 +401,25 @@ func (c *controlBuffer) throttle() { } } +// throttleWithTimeout is like throttle, but it stops waiting and returns an +// error if the control buffer is still throttled after the given duration. +func (c *controlBuffer) throttleWithTimeout(timeout time.Duration) error { + ch := c.trfChan.Load() + if ch == nil { + return nil + } + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case <-(*ch): + return nil + case <-c.done: + return nil + case <-timer.C: + return fmt.Errorf("still throttled after %v, with at least %d queued frames", timeout, maxQueuedTransportResponseFrames) + } +} + // put adds an item to the controlbuf. func (c *controlBuffer) put(it cbItem) error { _, err := c.executeAndPut(nil, it) @@ -390,9 +455,9 @@ func (c *controlBuffer) executeAndPut(f func() bool, it cbItem) (bool, error) { c.consumerWaiting = false } c.list.enqueue(it) - if it.isThrottled() { + if it.isTransportResponseFrame(c.side) { c.transportResponseFrames++ - if c.transportResponseFrames == maxQueuedControlBufferItems { + if c.transportResponseFrames == maxQueuedTransportResponseFrames { // We are adding the frame that puts us over the threshold; create // a throttling channel. ch := make(chan struct{}) @@ -447,8 +512,8 @@ func (c *controlBuffer) getOnceLocked() (any, error) { return nil, nil } h := c.list.dequeue().(cbItem) - if h.isThrottled() { - if c.transportResponseFrames == maxQueuedControlBufferItems { + if h.isTransportResponseFrame(c.side) { + if c.transportResponseFrames == maxQueuedTransportResponseFrames { // We are removing the frame that put us over the // threshold; close and clear the throttling channel. ch := c.trfChan.Swap(nil) @@ -484,9 +549,10 @@ func (c *controlBuffer) finish() { } } - // In case throttle() is currently in flight, it needs to be unblocked. - // Otherwise, the transport may not close, since the transport is closed by - // the reader encountering the connection error. + // In case throttle() or throttleWithTimeout() is currently in flight, + // it needs to be unblocked. Otherwise, the transport may not close, + // since the transport is closed by the reader encountering the + // connection error. ch := c.trfChan.Swap(nil) if ch != nil { close(*ch) diff --git a/vendor/google.golang.org/grpc/internal/transport/handler_server.go b/vendor/google.golang.org/grpc/internal/transport/handler_server.go index 9cd8d28d..a701522b 100644 --- a/vendor/google.golang.org/grpc/internal/transport/handler_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/handler_server.go @@ -268,7 +268,7 @@ func (ht *serverHandlerTransport) writeStatus(s *ServerStream, st *status.Status for _, v := range vv { // http2 ResponseWriter mechanism to send undeclared Trailers after // the headers have possibly been written. - h.Add(http2.TrailerPrefix+k, encodeMetadataHeader(k, v)) + h.Add(http.TrailerPrefix+k, encodeMetadataHeader(k, v)) } } } @@ -494,7 +494,8 @@ func mapRecvMsgError(err error) error { if err == io.EOF || err == io.ErrUnexpectedEOF { return err } - if se, ok := err.(http2.StreamError); ok { + var se http2.StreamError + if errors.As(err, &se) { if code, ok := http2ErrConvTab[se.Code]; ok { return status.Error(code, se.Error()) } diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 720fc4e5..a198bafb 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -65,6 +65,13 @@ var clientConnectionCounter uint64 var goAwayLoopyWriterTimeout = 5 * time.Second +// clientThrottleTimeout is the maximum amount of time for which the client +// stops reading from the connection because too many frames queued in +// response to the server (see maxQueuedTransportResponseFrames) are waiting +// to be written. After that, the client closes the connection instead of +// waiting forever, since the server is most likely not reading. +var clientThrottleTimeout = 10 * time.Second + var metadataFromOutgoingContextRaw = internal.FromOutgoingContextRaw.(func(context.Context) (metadata.MD, [][]string, bool)) // http2Client implements the ClientTransport interface with HTTP2. @@ -107,6 +114,12 @@ type http2Client struct { kp keepalive.ClientParameters keepaliveEnabled bool + // throttleTimeout is the maximum amount of time for which reads can + // stay throttled before the connection is closed. It is copied from + // clientThrottleTimeout on creation, so that tests can override the + // latter without racing with the readers of other transports. + throttleTimeout time.Duration + statsHandler stats.Handler initialWindowSize int32 @@ -359,6 +372,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts streamQuota: defaultMaxStreamsClient, streamsQuotaAvailable: make(chan struct{}, 1), keepaliveEnabled: keepaliveEnabled, + throttleTimeout: clientThrottleTimeout, bufferPool: opts.BufferPool, onClose: onClose, } @@ -386,7 +400,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts } else if md := imetadata.Get(addr); md != nil { t.md = md } - t.controlBuf = newControlBuffer(t.ctxDone) + t.controlBuf = newControlBuffer(t.ctxDone, clientSide) if opts.InitialWindowSize >= defaultWindowSize { t.initialWindowSize = opts.InitialWindowSize } @@ -952,6 +966,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr, handler s LocalAddr: t.localAddr, Compression: callHdr.SendCompress, Header: header, + Authority: callHdr.Host, }) } if transportDrainRequired { @@ -1726,9 +1741,19 @@ func (t *http2Client) reader(errCh chan<- error) { atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) } - // loop to keep reading incoming messages on this transport. + // Loop to keep reading incoming messages on this transport. for { - t.controlBuf.throttle() + // Stop reading while too many items that the server can make + // the client queue (see cbItem.isTransportResponseFrame) are + // waiting to be written, to bound the memory that a server + // can make the client use by flooding it without reading. If + // reads stay throttled for t.throttleTimeout, close the + // connection instead of waiting forever. + if err := t.controlBuf.throttleWithTimeout(t.throttleTimeout); err != nil { + t.logger.Warningf("Closing connection since the server is not reading: %v", err) + errClose = connectionErrorf(true, err, "closing connection since the server is not reading: %v", err) + return + } frame, err := t.framer.readFrame() if t.keepaliveEnabled { atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index f11e88cc..5c3fc765 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -297,7 +297,7 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, ) t.logger = prefixLoggerForServerTransport(t) - t.controlBuf = newControlBuffer(t.done) + t.controlBuf = newControlBuffer(t.done, serverSide) if !config.StaticWindowSize { t.bdpEst = &bdpEstimator{ bdp: initialWindowSize, @@ -380,6 +380,11 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade defer t.maxStreamMu.Unlock() streamID := frame.Header().StreamID + if streamID%2 != 1 || streamID <= t.maxStreamID { + // illegal gRPC stream id. + return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame) + } + t.maxStreamID = streamID // frame.Truncated is set to true when framer detects that the current header // list size hits MaxHeaderListSize limit. @@ -393,12 +398,6 @@ func (t *http2Server) operateHeaders(ctx context.Context, frame *http2.MetaHeade return nil } - if streamID%2 != 1 || streamID <= t.maxStreamID { - // illegal gRPC stream id. - return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame) - } - t.maxStreamID = streamID - s := &ServerStream{ Stream: Stream{ id: streamID, @@ -1014,7 +1013,17 @@ func (t *http2Server) streamContextErr(s *ServerStream) error { return ErrConnClosing default: } - return ContextErr(s.ctx.Err()) + err := s.ctx.Err() + if err == nil { + // In closeStream and finishStream, the stream state is transitioned to + // streamDone before s.cancel() is invoked so that concurrent operations + // immediately observe the terminal state. A concurrent caller (such as + // write or writeHeader) can see streamDone and invoke streamContextErr + // before s.cancel() runs or propagates to s.ctx. Default to + // context.Canceled rather than passing nil to ContextErr. + err = context.Canceled + } + return ContextErr(err) } // WriteHeader sends the header metadata md back to the client. @@ -1336,10 +1345,6 @@ 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 *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. - s.cancel() oldState := s.swapState(streamDone) if oldState == streamDone { @@ -1347,6 +1352,11 @@ func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrC return } + // 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. + s.cancel() + hdr.cleanup = &cleanupStream{ streamID: s.id, rst: rst, @@ -1360,15 +1370,16 @@ func (t *http2Server) finishStream(s *ServerStream, rst bool, rstCode http2.ErrC // closeStream clears the footprint of a stream when the stream is not needed any more. func (t *http2Server) closeStream(s *ServerStream, rst bool, rstCode http2.ErrCode, eosReceived bool) { + // We can't return early even if the stream's state is "done" as the state + // might have been set by the `finishStream` method. Deleting the stream via + // `finishStream` can get blocked on flow control. + s.swapState(streamDone) + // 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. s.cancel() - // We can't return early even if the stream's state is "done" as the state - // might have been set by the `finishStream` method. Deleting the stream via - // `finishStream` can get blocked on flow control. - s.swapState(streamDone) t.deleteStream(s, eosReceived) t.controlBuf.put(&cleanupStream{ diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index fc656b36..f627adf6 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -121,7 +121,9 @@ func (b *recvBuffer) put(r recvMsg) { 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() + if r.buffer != nil { + r.buffer.Free() + } // An error had occurred earlier, don't accept more // data or errors. return diff --git a/vendor/google.golang.org/grpc/metadata/metadata.go b/vendor/google.golang.org/grpc/metadata/metadata.go index 2b79204b..a3090ff3 100644 --- a/vendor/google.golang.org/grpc/metadata/metadata.go +++ b/vendor/google.golang.org/grpc/metadata/metadata.go @@ -360,6 +360,66 @@ func FromOutgoingContext(ctx context.Context) (MD, bool) { return out, ok } +// ValueFromOutgoingContext returns the metadata value corresponding to the +// metadata key from the outgoing metadata if it exists. Keys are matched in a +// case-insensitive manner. +// +// Unlike FromOutgoingContext, this does not copy the entire outgoing metadata. +func ValueFromOutgoingContext(ctx context.Context, key string) []string { + raw, ok := ctx.Value(mdOutgoingKey{}).(rawMD) + if !ok { + return nil + } + + key = strings.ToLower(key) + // Unlike ValueFromIncomingContext, this can't return as soon as raw.md is + // checked: raw.added still needs to be walked and accumulated regardless. + // The else below prevents that from overwriting an exact match with an + // unrelated case-insensitive one. + var matchedMD []string + if v, ok := raw.md[key]; ok { + matchedMD = v + } else { + for k, v := range raw.md { + // Case insensitive comparison: MD is a map, and there's no + // guarantee that the MD attached to the context is created using + // our helper functions. + if strings.EqualFold(k, key) { + matchedMD = v + break + } + } + } + + // Defer allocating vals until raw.added actually has a match, so a match + // found only in raw.md doesn't pay for both a copyOf and a growing + // append. + var vals []string + for _, added := range raw.added { + if len(added)%2 == 1 { + panic(fmt.Sprintf("metadata: ValueFromOutgoingContext got an odd number of input pairs for metadata: %d", len(added))) + } + + // Case insensitive, like FromOutgoingContext: added isn't guaranteed + // lowercase, though AppendToOutgoingContext already lowercases it in + // practice, so try the cheap exact match first. + for i := 0; i < len(added); i += 2 { + if added[i] == key || strings.EqualFold(added[i], key) { + if vals == nil { + vals = make([]string, 0, len(matchedMD)+1) + vals = append(vals, matchedMD...) + } + vals = append(vals, added[i+1]) + } + } + } + + if vals == nil && matchedMD != nil { + return copyOf(matchedMD) + } + return vals +} + type rawMD struct { md MD added [][]string diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index 52f4ea51..6f1bb2ea 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -179,7 +179,6 @@ type callInfo struct { contentSubtype string codec baseCodec maxRetryRPCBufferSize int - onFinish []func(err error) authority string acceptedResponseCompressors []string } @@ -394,8 +393,7 @@ type OnFinishCallOption struct { OnFinish func(error) } -func (o OnFinishCallOption) before(c *callInfo) error { - c.onFinish = append(c.onFinish, o.OnFinish) +func (o OnFinishCallOption) before(*callInfo) error { return nil } diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 4ab54115..5c165972 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -1400,7 +1400,7 @@ func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, ss.decompressorV1 = encoding.GetCompressor(rc) if ss.decompressorV1 == nil { st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc) - ss.writeStatus(st) + ss.s.WriteStatus(st) return st.Err() } } @@ -1491,7 +1491,7 @@ func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, binlog.Log(ctx, st) } } - if err := ss.writeStatus(appStatus); err != nil { + if err := ss.s.WriteStatus(appStatus); err != nil { channelz.Warningf(logger, s.channelz, "grpc: Server.processRPC failed to write status: %v", err) } return appErr @@ -1510,7 +1510,7 @@ func (s *Server) processRPC(ctx context.Context, stream *transport.ServerStream, binlog.Log(ctx, st) } } - if err := ss.writeStatus(statusOK); err != nil { + if err := ss.s.WriteStatus(statusOK); err != nil { channelz.Warningf(logger, s.channelz, "grpc: Server.processRPC failed to write status: %v", err) } return err @@ -1719,7 +1719,13 @@ func (s *Server) stop(graceful bool) { } if graceful || s.opts.waitForHandlers { + // Release the lock while waiting for handlers to finish. Holding it + // here would deadlock a concurrent Stop() (the recommended way to abort + // a GracefulStop that is taking too long), because Stop() must acquire + // s.mu before it can force the server to shut down. + s.mu.Unlock() s.handlersWG.Wait() + s.mu.Lock() } if s.events != nil { diff --git a/vendor/google.golang.org/grpc/stats/stats.go b/vendor/google.golang.org/grpc/stats/stats.go index 10bf998a..c6996724 100644 --- a/vendor/google.golang.org/grpc/stats/stats.go +++ b/vendor/google.golang.org/grpc/stats/stats.go @@ -190,6 +190,9 @@ type OutHeader struct { Compression string // Header contains the header metadata sent. Header metadata.MD + // Authority is the :authority pseudo-header sent for the RPC. + // It is valid only if Client is true. + Authority string // The following fields are valid only if Client is true. // FullMethod is the full RPC method string, i.e., /package.service/method. diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index 52e59f2c..9fd628b8 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -201,9 +201,9 @@ func (w *clientStreamWrapper) SendMsg(m any) error { } -// RecvMsg receives message m from the stream. For RPCs that call RecvMsg only -// once i.e. only client streaming RPCs, it calls the underlying RecvMsg a -// second time after receiving the first message to get the trailers. +// RecvMsg receives message m from the stream. For non-server-streaming RPCs +// (unary and client-streaming), it calls the underlying RecvMsg a second time +// after receiving the first message to get the trailers. func (w *clientStreamWrapper) RecvMsg(m any) error { err := w.ClientStream.RecvMsg(m) if err != nil { @@ -224,19 +224,6 @@ func (w *clientStreamWrapper) RecvMsg(m any) error { return err } -// defaultStreamInterceptor is a StreamClientInterceptor which wraps the -// ClientStream and is always invoked as the first interceptor. It consolidates -// behavior for different RPC types at the level closest to the application, -// which simplifies the underlying stream implementation and other interceptors -// by avoiding duplicate or scattered handling. -func defaultStreamInterceptor(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error) { - cs, err := streamer(ctx, desc, cc, method, opts...) - if err != nil { - return nil, err - } - return &clientStreamWrapper{ClientStream: cs, desc: desc}, nil -} - // NewStream creates a new Stream for the client side. This is typically // called by generated code. ctx is used for the lifetime of the stream. // @@ -392,7 +379,11 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } } - return newStream(ctx, opts...) + cs, err := newStream(ctx, opts...) + if err != nil { + return nil, err + } + return &clientStreamWrapper{ClientStream: cs, desc: desc}, nil } func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc *serviceconfig.MethodConfig, onCommit func(), nameResolutionDelayed bool, opts ...CallOption) (_ ClientStream, err error) { @@ -632,7 +623,12 @@ func (a *csAttempt) getTransport() error { func (a *csAttempt) newStream() error { cs := a.cs - cs.callHdr.PreviousAttempts = cs.numRetries + // The header is copied because the fields set below, notably the authority + // override taken from the pick result, describe the endpoint picked for + // this attempt only. Mutating the clientStream's header would carry them + // into a later attempt. + callHdr := *cs.callHdr + callHdr.PreviousAttempts = cs.numRetries // Merge metadata stored in PickResult, if any, with existing call metadata. // It is safe to overwrite the csAttempt's context here, since all state @@ -659,11 +655,11 @@ func (a *csAttempt) newStream() error { // apply it, as specified in gRFC A81. if cs.callInfo.authority == "" { if authMD := a.pickResult.Metadata.Get(":authority"); len(authMD) > 0 { - cs.callHdr.Authority = authMD[0] + callHdr.Authority = authMD[0] } } } - s, err := a.transport.NewStream(a.ctx, cs.callHdr, a.statsHandler) + s, err := a.transport.NewStream(a.ctx, &callHdr, a.statsHandler) if err != nil { nse, ok := err.(*transport.NewStreamError) if !ok { @@ -698,10 +694,6 @@ type clientStream struct { cancel context.CancelFunc // cancels all attempts - sentLast bool // sent an end stream - - receivedFirstMsg bool // set after the first message is received - methodConfig *MethodConfig ctx context.Context // the application's context, wrapped by stats/tracing @@ -709,19 +701,10 @@ type clientStream struct { retryThrottler *retryThrottler // The throttler active when the RPC began. binlogs []binarylog.MethodLogger - // serverHeaderBinlogged is a boolean for whether server header has been - // logged. Server header will be logged when the first time one of those - // happens: stream.Header(), stream.Recv(). - // - // It's only read and used by Recv() and Header(), so it doesn't need to be - // synchronized. - serverHeaderBinlogged bool mu sync.Mutex - firstAttempt bool // if true, transparent retry is valid - numRetries int // exclusive of transparent retry attempt(s) - numRetriesSincePushback int // retries since pushback; to reset backoff - finished bool // TODO: replace with atomic cmpxchg or sync.Once? + numRetries int // exclusive of transparent retry attempt(s) + numRetriesSincePushback int // retries since pushback; to reset backoff // attempt is the active client stream attempt. // The only place where it is written is the newAttemptLocked method and this method never writes nil. // So, attempt can be nil only inside newClientStream function when clientStream is first created. @@ -731,13 +714,33 @@ type clientStream struct { // place where we need to check if the attempt is nil. attempt *csAttempt // TODO(hedging): hedging will have multiple attempts simultaneously. - committed bool // active attempt committed for retry? onCommit func() replayBuffer []replayOp // operations to replay on retry replayBufferSize int // current size of replayBuffer + + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9280 for benchmarks. + // Add new bool fields here, not inline above. + + // Not guarded by mu. + sentLast bool // sent an end stream + receivedFirstMsg bool // set after the first message is received + // serverHeaderBinlogged is a boolean for whether server header has been + // logged. Server header will be logged when the first time one of those + // happens: stream.Header(), stream.Recv(). + // + // It's only read and used by Recv() and Header(), so it doesn't need to be + // synchronized. + serverHeaderBinlogged bool // nameResolutionDelay indicates if there was a delay in the name resolution. // This field is only valid on client side, it's always false on server side. nameResolutionDelay bool + + // Guarded by mu. + firstAttempt bool // if true, transparent retry is valid + finished bool // TODO: replace with atomic cmpxchg or sync.Once? + committed bool // active attempt committed for retry? } type replayOp struct { @@ -755,10 +758,8 @@ type csAttempt struct { parser parser pickResult balancer.PickResult - finished bool - decompressorV0 Decompressor - decompressorV1 encoding.Compressor - decompressorSet bool + decompressorV0 Decompressor + decompressorV1 encoding.Compressor mu sync.Mutex // guards trInfo.tr // trInfo may be nil (if EnableTracing is false). @@ -769,10 +770,18 @@ type csAttempt struct { statsHandler stats.Handler beginTime time.Time - // set for newStream errors that may be transparently retried - allowTransparentRetry bool - // set for pick errors that are returned as a status - drop bool + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9347 for benchmarks. + // Add new bool fields here, not inline above. + + // Not guarded by mu. + decompressorSet bool + allowTransparentRetry bool // set for newStream errors that may be transparently retried + drop bool // set for pick errors that are returned as a status + + // Guarded by mu. + finished bool } func (cs *clientStream) commitAttemptLocked() { @@ -1500,20 +1509,28 @@ type addrConnStream struct { callInfo *callInfo transport transport.ClientTransport ctx context.Context - sentLast bool - receivedFirstMsg bool desc *StreamDesc codec baseCodec sendCompressorV0 Compressor sendCompressorV1 encoding.Compressor - decompressorSet bool decompressorV0 Decompressor decompressorV1 encoding.Compressor - parser parser // mu guards finished and is held for the entire finish method. - mu sync.Mutex - finished bool + mu sync.Mutex + parser parser + + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9348 for benchmarks. + // Add new bool fields here, not inline above. + + // Not guarded by mu. + sentLast bool + receivedFirstMsg bool + decompressorSet bool + + finished bool // guarded by mu } func (as *addrConnStream) Header() (metadata.MD, error) { @@ -1728,15 +1745,23 @@ type serverStream struct { sendCompressorName string - recvFirstMsg bool // set after the first message is received - maxReceiveMessageSize int maxSendMessageSize int - trInfo *traceInfo + + // mu guards trInfo.tr after the service handler runs. + mu sync.Mutex + trInfo *traceInfo statsHandler stats.Handler binlogs []binarylog.MethodLogger + + // Bool fields are grouped at the tail to eliminate the alignment padding + // that would otherwise follow each bool when the next field is pointer- or + // int-sized. See https://github.com/grpc/grpc-go/issues/9349 for benchmarks. + // Add new bool fields here, not inline above. + + recvFirstMsg bool // set after the first message is received // serverHeaderBinlogged indicates whether server header has been logged. It // will happen when one of the following two happens: stream.SendHeader(), // stream.Send(). @@ -1744,10 +1769,6 @@ type serverStream struct { // It's only checked in send and sendHeader, doesn't need to be // synchronized. serverHeaderBinlogged bool - - statusWritten atomic.Bool // True if status has been written to the transport. - - mu sync.Mutex // protects trInfo.tr after the service handler runs. } func (ss *serverStream) Context() context.Context { @@ -1795,16 +1816,6 @@ func (ss *serverStream) SetTrailer(md metadata.MD) { ss.s.SetTrailer(md) } -// writeStatus sends the status of a stream to the client. It uses an atomic -// CAS to guarantee that the status is written to the transport exactly once, -// even if called concurrently. -func (ss *serverStream) writeStatus(st *status.Status) error { - if !ss.statusWritten.CompareAndSwap(false, true) { - return nil - } - return ss.s.WriteStatus(st) -} - func (ss *serverStream) SendMsg(m any) (err error) { defer func() { if ss.trInfo != nil { @@ -1821,7 +1832,7 @@ func (ss *serverStream) SendMsg(m any) (err error) { } if err != nil && err != io.EOF { st, _ := status.FromError(toRPCErr(err)) - ss.writeStatus(st) + ss.s.WriteStatus(st) // Non-user specified status was sent out. This should be an error // case (as a server side Cancel maybe). // @@ -1904,7 +1915,7 @@ func (ss *serverStream) RecvMsg(m any) (err error) { } if err != nil && err != io.EOF { st, _ := status.FromError(toRPCErr(err)) - ss.writeStatus(st) + ss.s.WriteStatus(st) // Non-user specified status was sent out. This should be an error // case (as a server side Cancel maybe). // diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 75f8ef50..d41341bc 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.84.0" +const Version = "1.85.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index 40b93f14..57bc65f4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -37,7 +37,7 @@ github.com/go-kit/log/level # github.com/go-logfmt/logfmt v0.5.1 ## explicit; go 1.17 github.com/go-logfmt/logfmt -# github.com/go-logr/logr v1.4.3 +# github.com/go-logr/logr v1.4.4 ## explicit; go 1.18 github.com/go-logr/logr # github.com/go-viper/mapstructure/v2 v2.4.0 @@ -125,7 +125,7 @@ github.com/subosito/gotenv # go.yaml.in/yaml/v3 v3.0.5 ## explicit; go 1.16 go.yaml.in/yaml/v3 -# golang.org/x/net v0.59.0 +# golang.org/x/net v0.60.0 ## explicit; go 1.26.0 golang.org/x/net/http/httpguts golang.org/x/net/http2 @@ -151,11 +151,11 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 +# google.golang.org/genproto/googleapis/rpc v0.0.0-20260817212433-ac3dfec99bb1 ## explicit; go 1.25.0 google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.84.0 -## explicit; go 1.25.0 +# google.golang.org/grpc v1.85.0 +## explicit; go 1.26.0 google.golang.org/grpc google.golang.org/grpc/attributes google.golang.org/grpc/backoff