From 9875111176f498e5a01fde2149140b8750a39b19 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Thu, 16 Oct 2025 16:27:17 +0530 Subject: [PATCH 01/11] changes no tests --- internal/transport/http2_client.go | 37 ++++++----- internal/transport/http2_server.go | 37 ++++++----- internal/transport/http_util.go | 91 +++++++++++++++++++++++++++- internal/transport/keepalive_test.go | 3 +- 4 files changed, 134 insertions(+), 34 deletions(-) diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index 911d7e1ea4e3..7fa369d31e37 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -335,7 +335,7 @@ func NewHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts writerDone: make(chan struct{}), goAway: make(chan struct{}), keepaliveDone: make(chan struct{}), - framer: newFramer(conn, writeBufSize, readBufSize, opts.SharedWriteBuffer, maxHeaderListSize), + framer: newFramer(conn, writeBufSize, readBufSize, opts.SharedWriteBuffer, maxHeaderListSize, opts.BufferPool), fc: &trInFlow{limit: uint32(icwz)}, scheme: scheme, activeStreams: make(map[uint32]*ClientStream), @@ -1177,7 +1177,12 @@ func (t *http2Client) updateFlowControl(n uint32) { }) } -func (t *http2Client) handleData(f *http2.DataFrame) { +func (t *http2Client) handleData(f *parsedDataFrame) { + defer func() { + if f.data != nil { + f.data.Free() + } + }() size := f.Header().Length var sendBDPPing bool if t.bdpEst != nil { @@ -1221,22 +1226,15 @@ func (t *http2Client) handleData(f *http2.DataFrame) { t.closeStream(s, io.EOF, true, http2.ErrCodeFlowControl, status.New(codes.Internal, err.Error()), nil, false) return } + dataLen := f.data.Len() if f.Header().Flags.Has(http2.FlagDataPadded) { - if w := s.fc.onRead(size - uint32(len(f.Data()))); w > 0 { + if w := s.fc.onRead(size - uint32(dataLen)); w > 0 { t.controlBuf.put(&outgoingWindowUpdate{s.id, w}) } } - // TODO(bradfitz, zhaoq): A copy is required here because there is no - // guarantee f.Data() is consumed before the arrival of next frame. - // Can this copy be eliminated? - if len(f.Data()) > 0 { - pool := t.bufferPool - if pool == nil { - // Note that this is only supposed to be nil in tests. Otherwise, stream is - // always initialized with a BufferPool. - pool = mem.DefaultBufferPool() - } - s.write(recvMsg{buffer: mem.Copy(f.Data(), pool)}) + if dataLen > 0 { + s.write(recvMsg{buffer: f.data}) + f.data = nil } } // The server has closed the stream without sending trailers. Record that @@ -1656,6 +1654,13 @@ func (t *http2Client) reader(errCh chan<- error) { } }() + pool := t.bufferPool + if pool == nil { + // Note that this is only supposed to be nil in tests. Otherwise, stream + // is always initialized with a BufferPool. + pool = mem.DefaultBufferPool() + } + if err := t.readServerPreface(); err != nil { errCh <- err return @@ -1668,7 +1673,7 @@ func (t *http2Client) reader(errCh chan<- error) { // loop to keep reading incoming messages on this transport. for { t.controlBuf.throttle() - frame, err := t.framer.fr.ReadFrame() + frame, err := t.framer.readFrame() if t.keepaliveEnabled { atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) } @@ -1701,7 +1706,7 @@ func (t *http2Client) reader(errCh chan<- error) { switch frame := frame.(type) { case *http2.MetaHeadersFrame: t.operateHeaders(frame) - case *http2.DataFrame: + case *parsedDataFrame: t.handleData(frame) case *http2.RSTStreamFrame: t.handleRSTStream(frame) diff --git a/internal/transport/http2_server.go b/internal/transport/http2_server.go index bcedac32fed5..edcf9ecc8b9b 100644 --- a/internal/transport/http2_server.go +++ b/internal/transport/http2_server.go @@ -169,7 +169,8 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, if config.MaxHeaderListSize != nil { maxHeaderListSize = *config.MaxHeaderListSize } - framer := newFramer(conn, writeBufSize, readBufSize, config.SharedWriteBuffer, maxHeaderListSize) + + framer := newFramer(conn, writeBufSize, readBufSize, config.SharedWriteBuffer, maxHeaderListSize, config.BufferPool) // Send initial settings as connection preface to client. isettings := []http2.Setting{{ ID: http2.SettingMaxFrameSize, @@ -668,10 +669,16 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStre close(t.readerDone) <-t.loopyWriterDone }() + pool := t.bufferPool + if pool == nil { + // Note that this is only supposed to be nil in tests. Otherwise, stream + // is always initialized with a BufferPool. + pool = mem.DefaultBufferPool() + } for { t.controlBuf.throttle() - frame, err := t.framer.fr.ReadFrame() atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) + frame, err := t.framer.readFrame() if err != nil { if se, ok := err.(http2.StreamError); ok { if t.logger.V(logLevel) { @@ -707,7 +714,7 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStre }) continue } - case *http2.DataFrame: + case *parsedDataFrame: t.handleData(frame) case *http2.RSTStreamFrame: t.handleRSTStream(frame) @@ -788,7 +795,12 @@ func (t *http2Server) updateFlowControl(n uint32) { } -func (t *http2Server) handleData(f *http2.DataFrame) { +func (t *http2Server) handleData(f *parsedDataFrame) { + defer func() { + if f.data != nil { + f.data.Free() + } + }() size := f.Header().Length var sendBDPPing bool if t.bdpEst != nil { @@ -833,22 +845,15 @@ func (t *http2Server) handleData(f *http2.DataFrame) { t.closeStream(s, true, http2.ErrCodeFlowControl, false) return } + dataLen := f.data.Len() if f.Header().Flags.Has(http2.FlagDataPadded) { - if w := s.fc.onRead(size - uint32(len(f.Data()))); w > 0 { + if w := s.fc.onRead(size - uint32(dataLen)); w > 0 { t.controlBuf.put(&outgoingWindowUpdate{s.id, w}) } } - // TODO(bradfitz, zhaoq): A copy is required here because there is no - // guarantee f.Data() is consumed before the arrival of next frame. - // Can this copy be eliminated? - if len(f.Data()) > 0 { - pool := t.bufferPool - if pool == nil { - // Note that this is only supposed to be nil in tests. Otherwise, stream is - // always initialized with a BufferPool. - pool = mem.DefaultBufferPool() - } - s.write(recvMsg{buffer: mem.Copy(f.Data(), pool)}) + if dataLen > 0 { + s.write(recvMsg{buffer: f.data}) + f.data = nil } } if f.StreamEnded() { diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index e3663f87f391..05c23827c2ef 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -37,6 +37,7 @@ import ( "golang.org/x/net/http2" "golang.org/x/net/http2/hpack" "google.golang.org/grpc/codes" + "google.golang.org/grpc/mem" ) const ( @@ -388,15 +389,34 @@ func toIOError(err error) error { return ioError{error: err} } +type parsedDataFrame struct { + http2.FrameHeader + data mem.Buffer +} + +func (df *parsedDataFrame) StreamEnded() bool { + return df.FrameHeader.Flags.Has(http2.FlagDataEndStream) +} + type framer struct { writer *bufWriter fr *http2.Framer + reader io.Reader + // Cached data frame to avoid heap allocations. + dataFrame parsedDataFrame + pool mem.BufferPool } var writeBufferPoolMap = make(map[int]*sync.Pool) var writeBufferMutex sync.Mutex -func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32) *framer { +func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32, memPool mem.BufferPool) *framer { + if memPool == nil { + // Note that this is only supposed to be nil in tests. Otherwise, stream + // is always initialized with a BufferPool. + memPool = mem.DefaultBufferPool() + } + if writeBufferSize < 0 { writeBufferSize = 0 } @@ -412,6 +432,8 @@ func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBu f := &framer{ writer: w, fr: http2.NewFramer(w, r), + reader: r, + pool: memPool, } f.fr.SetMaxReadFrameSize(http2MaxFrameLen) // Opt-in to Frame reuse API on framer to reduce garbage. @@ -422,6 +444,73 @@ func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBu return f } +func (f *framer) readFrame() (any, error) { + fh, err := f.fr.ReadFrameHeader() + if err != nil { + return nil, err + } + if fh.Type == http2.FrameData { + err = f.readDataFrame(fh, f.pool, &f.dataFrame) + return &f.dataFrame, err + } else { + return f.fr.ReadFrameForHeader(fh) + } +} + +// readDataFrame reads and parses a data frame from the underlying io.Reader. +// Frames aren't safe to read from after a subsequent call to ReadFrame. +func (f *framer) readDataFrame(fh http2.FrameHeader, pool mem.BufferPool, df *parsedDataFrame) (err error) { + if fh.StreamID == 0 { + // DATA frames MUST be associated with a stream. If a + // DATA frame is received whose stream identifier + // field is 0x0, the recipient MUST respond with a + // connection error (Section 5.4.1) of type + // PROTOCOL_ERROR. + return fmt.Errorf("DATA frame with stream ID 0") + } + payload := pool.Get(int(fh.Length)) + defer func() { + if err != nil { + f.pool.Put(payload) + } + }() + if fh.Flags.Has(http2.FlagDataPadded) { + if fh.Length == 0 { + return io.ErrUnexpectedEOF + } + // This initial 1-byte read can be inefficient for unbuffered readers, + // but it allows the rest of the payload to be read directly to the + // start of the destination slice. This makes it easy to return the + // original slice back to the buffer pool. + if _, err := io.ReadFull(f.reader, (*payload)[:1]); err != nil { + return err + } + padSize := (*payload)[0] + *payload = (*payload)[:len(*payload)-1] + if _, err := io.ReadFull(f.reader, *payload); err != nil { + return err + } + if int(padSize) > len(*payload) { + // If the length of the padding is greater than the + // length of the frame payload, the recipient MUST + // treat this as a connection error. + // Filed: https://github.com/http2/http2-spec/issues/610 + return fmt.Errorf("pad size larger than data payload") + } + *payload = (*payload)[:len(*payload)-int(padSize)] + } else if _, err := io.ReadFull(f.reader, *payload); err != nil { + return err + } + + df.FrameHeader = fh + df.data = mem.NewBuffer(payload, pool) + return nil +} + +func (df *parsedDataFrame) Header() http2.FrameHeader { + return df.FrameHeader +} + func getWriteBufferPool(size int) *sync.Pool { writeBufferMutex.Lock() defer writeBufferMutex.Unlock() diff --git a/internal/transport/keepalive_test.go b/internal/transport/keepalive_test.go index 0bd7ba356b3c..962db8dfe583 100644 --- a/internal/transport/keepalive_test.go +++ b/internal/transport/keepalive_test.go @@ -40,6 +40,7 @@ import ( "google.golang.org/grpc/internal/grpctest" "google.golang.org/grpc/internal/syscall" "google.golang.org/grpc/keepalive" + "google.golang.org/grpc/mem" "google.golang.org/grpc/testdata" ) @@ -192,7 +193,7 @@ func (s) TestKeepaliveServerClosesUnresponsiveClient(t *testing.T) { if n, err := conn.Write(clientPreface); err != nil || n != len(clientPreface) { t.Fatalf("conn.Write(clientPreface) failed: n=%v, err=%v", n, err) } - framer := newFramer(conn, defaultWriteBufSize, defaultReadBufSize, false, 0) + framer := newFramer(conn, defaultWriteBufSize, defaultReadBufSize, false, 0, mem.DefaultBufferPool()) if err := framer.fr.WriteSettings(http2.Setting{}); err != nil { t.Fatal("framer.WriteSettings(http2.Setting{}) failed:", err) } From a6f1b72e8c81bf11365457606523902aa71d3401 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Thu, 16 Oct 2025 18:19:36 +0530 Subject: [PATCH 02/11] tests --- internal/transport/http_util.go | 7 ++- internal/transport/http_util_test.go | 77 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index 05c23827c2ef..7f924f3e4d5a 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -25,7 +25,6 @@ import ( "fmt" "io" "math" - "net" "net/http" "net/url" "strconv" @@ -301,11 +300,11 @@ type bufWriter struct { buf []byte offset int batchSize int - conn net.Conn + conn io.ReadWriter err error } -func newBufWriter(conn net.Conn, batchSize int, pool *sync.Pool) *bufWriter { +func newBufWriter(conn io.ReadWriter, batchSize int, pool *sync.Pool) *bufWriter { w := &bufWriter{ batchSize: batchSize, conn: conn, @@ -410,7 +409,7 @@ type framer struct { var writeBufferPoolMap = make(map[int]*sync.Pool) var writeBufferMutex sync.Mutex -func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32, memPool mem.BufferPool) *framer { +func newFramer(conn io.ReadWriter, writeBufferSize, readBufferSize int, sharedWriteBuffer bool, maxHeaderListSize uint32, memPool mem.BufferPool) *framer { if memPool == nil { // Note that this is only supposed to be nil in tests. Otherwise, stream // is always initialized with a BufferPool. diff --git a/internal/transport/http_util_test.go b/internal/transport/http_util_test.go index 7fb2cdf46100..69e631d8b215 100644 --- a/internal/transport/http_util_test.go +++ b/internal/transport/http_util_test.go @@ -19,6 +19,7 @@ package transport import ( + "bytes" "errors" "fmt" "io" @@ -27,6 +28,8 @@ import ( "reflect" "testing" "time" + + "google.golang.org/grpc/mem" ) func (s) TestDecodeTimeout(t *testing.T) { @@ -295,3 +298,77 @@ func BenchmarkEncodeGrpcMessage(b *testing.B) { } } } + +func (s) TestFrame_ReadDataFrame(t *testing.T) { + tests := []struct { + name string + w func(fr *framer) error + wantData []byte + wantErr bool + }{ + { + name: "good_padded", + w: func(fr *framer) error { + return fr.fr.WriteDataPadded(1, false, []byte("foo"), []byte{0, 0}) + }, + wantData: []byte("foo"), + }, + { + name: "good_unpadded", + w: func(fr *framer) error { + return fr.fr.WriteData(1, false, []byte("foo")) + }, + wantData: []byte("foo"), + }, + { + name: "padded_zero_data_some_padding", + w: func(fr *framer) error { + return fr.fr.WriteDataPadded(1, false, []byte{}, []byte{0, 0}) + }, + wantData: []byte{}, + }, + { + name: "stream_id_0", + w: func(fr *framer) error { + fr.fr.AllowIllegalWrites = true + return fr.fr.WriteData(0, false, []byte("foo")) + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fr, _ := testFramer() + // Write the frame using the provided function. + writeErr := tt.w(fr) + if writeErr != nil { + t.Fatalf("tt.w() returned unexpected error: %v", writeErr) + } + fr.writer.Flush() + + // Read the frame back. + f, err := fr.readFrame() + if gotErr := err != nil; gotErr != tt.wantErr { + t.Fatalf("ReadFrame() err = %v; want %t", err, tt.wantErr) + } + if tt.wantErr { + return + } + + df, ok := f.(*parsedDataFrame) + if !ok { + t.Fatalf("ReadFrame() returned %T, want *parsedDataFrame", f) + } + if gotData := df.data.ReadOnlyData(); !bytes.Equal(gotData, tt.wantData) { + t.Fatalf("parsedDataFrame.Data() = %q, want %q", gotData, tt.wantData) + } + df.data.Free() + }) + } +} + +func testFramer() (*framer, *bytes.Buffer) { + buf := new(bytes.Buffer) + return newFramer(buf, defaultWriteBufSize, defaultReadBufSize, false, defaultClientMaxHeaderListSize, mem.DefaultBufferPool()), buf +} From 3f984714f6740db652da42decf1d60d413eac296 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Thu, 16 Oct 2025 18:21:37 +0530 Subject: [PATCH 03/11] cleanup --- internal/transport/http2_client.go | 7 ------- internal/transport/http2_server.go | 9 +-------- internal/transport/http_util.go | 9 +++++---- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index 7fa369d31e37..926a5d4f00d4 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -1654,13 +1654,6 @@ func (t *http2Client) reader(errCh chan<- error) { } }() - pool := t.bufferPool - if pool == nil { - // Note that this is only supposed to be nil in tests. Otherwise, stream - // is always initialized with a BufferPool. - pool = mem.DefaultBufferPool() - } - if err := t.readServerPreface(); err != nil { errCh <- err return diff --git a/internal/transport/http2_server.go b/internal/transport/http2_server.go index edcf9ecc8b9b..cd01dc82b470 100644 --- a/internal/transport/http2_server.go +++ b/internal/transport/http2_server.go @@ -169,7 +169,6 @@ func NewServerTransport(conn net.Conn, config *ServerConfig) (_ ServerTransport, if config.MaxHeaderListSize != nil { maxHeaderListSize = *config.MaxHeaderListSize } - framer := newFramer(conn, writeBufSize, readBufSize, config.SharedWriteBuffer, maxHeaderListSize, config.BufferPool) // Send initial settings as connection preface to client. isettings := []http2.Setting{{ @@ -669,16 +668,10 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStre close(t.readerDone) <-t.loopyWriterDone }() - pool := t.bufferPool - if pool == nil { - // Note that this is only supposed to be nil in tests. Otherwise, stream - // is always initialized with a BufferPool. - pool = mem.DefaultBufferPool() - } for { t.controlBuf.throttle() - atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) frame, err := t.framer.readFrame() + atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) if err != nil { if se, ok := err.(http2.StreamError); ok { if t.logger.V(logLevel) { diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index 7f924f3e4d5a..c1ea41fc84cd 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -443,21 +443,22 @@ func newFramer(conn io.ReadWriter, writeBufferSize, readBufferSize int, sharedWr return f } +// readFrame reads a single frame. The returned Frame is only valid +// until the next call to readFrame. func (f *framer) readFrame() (any, error) { fh, err := f.fr.ReadFrameHeader() if err != nil { return nil, err } + // Read the data frame directly from the underlying io.Reader to avoid + // copies. if fh.Type == http2.FrameData { err = f.readDataFrame(fh, f.pool, &f.dataFrame) return &f.dataFrame, err - } else { - return f.fr.ReadFrameForHeader(fh) } + return f.fr.ReadFrameForHeader(fh) } -// readDataFrame reads and parses a data frame from the underlying io.Reader. -// Frames aren't safe to read from after a subsequent call to ReadFrame. func (f *framer) readDataFrame(fh http2.FrameHeader, pool mem.BufferPool, df *parsedDataFrame) (err error) { if fh.StreamID == 0 { // DATA frames MUST be associated with a stream. If a From 5fa01cd30b73cd2b3d638e56271c8df2ab83b09b Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Thu, 16 Oct 2025 23:35:48 +0530 Subject: [PATCH 04/11] eliminate extra heap alloc --- internal/transport/http2_client.go | 8 ++--- internal/transport/http2_server.go | 8 ++--- internal/transport/http_util.go | 53 ++++++++++++++++++++---------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index 926a5d4f00d4..cff2c616ad21 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -1178,11 +1178,6 @@ func (t *http2Client) updateFlowControl(n uint32) { } func (t *http2Client) handleData(f *parsedDataFrame) { - defer func() { - if f.data != nil { - f.data.Free() - } - }() size := f.Header().Length var sendBDPPing bool if t.bdpEst != nil { @@ -1233,8 +1228,8 @@ func (t *http2Client) handleData(f *parsedDataFrame) { } } if dataLen > 0 { + f.data.Ref() s.write(recvMsg{buffer: f.data}) - f.data = nil } } // The server has closed the stream without sending trailers. Record that @@ -1701,6 +1696,7 @@ func (t *http2Client) reader(errCh chan<- error) { t.operateHeaders(frame) case *parsedDataFrame: t.handleData(frame) + frame.data.Free() case *http2.RSTStreamFrame: t.handleRSTStream(frame) case *http2.SettingsFrame: diff --git a/internal/transport/http2_server.go b/internal/transport/http2_server.go index cd01dc82b470..d12c7af2f910 100644 --- a/internal/transport/http2_server.go +++ b/internal/transport/http2_server.go @@ -709,6 +709,7 @@ func (t *http2Server) HandleStreams(ctx context.Context, handle func(*ServerStre } case *parsedDataFrame: t.handleData(frame) + frame.data.Free() case *http2.RSTStreamFrame: t.handleRSTStream(frame) case *http2.SettingsFrame: @@ -789,11 +790,6 @@ func (t *http2Server) updateFlowControl(n uint32) { } func (t *http2Server) handleData(f *parsedDataFrame) { - defer func() { - if f.data != nil { - f.data.Free() - } - }() size := f.Header().Length var sendBDPPing bool if t.bdpEst != nil { @@ -845,8 +841,8 @@ func (t *http2Server) handleData(f *parsedDataFrame) { } } if dataLen > 0 { + f.data.Ref() s.write(recvMsg{buffer: f.data}) - f.data = nil } } if f.StreamEnded() { diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index c1ea41fc84cd..f4159690a203 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -453,13 +453,13 @@ func (f *framer) readFrame() (any, error) { // Read the data frame directly from the underlying io.Reader to avoid // copies. if fh.Type == http2.FrameData { - err = f.readDataFrame(fh, f.pool, &f.dataFrame) + err = f.readDataFrame(fh) return &f.dataFrame, err } return f.fr.ReadFrameForHeader(fh) } -func (f *framer) readDataFrame(fh http2.FrameHeader, pool mem.BufferPool, df *parsedDataFrame) (err error) { +func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { if fh.StreamID == 0 { // DATA frames MUST be associated with a stream. If a // DATA frame is received whose stream identifier @@ -468,12 +468,25 @@ func (f *framer) readDataFrame(fh http2.FrameHeader, pool mem.BufferPool, df *pa // PROTOCOL_ERROR. return fmt.Errorf("DATA frame with stream ID 0") } - payload := pool.Get(int(fh.Length)) - defer func() { - if err != nil { - f.pool.Put(payload) - } - }() + // Converting a *[]byte to a mem.BufferSlice incurs a heap allocation. This + // conversion is performed by mem.NewBuffer. To avoid the extra allocation + // a []byte is allocated directly if required and casted to a + // mem.BufferSlice. + var buf []byte + // poolHandle is the pointer returned by the buffer pool (if it's used.). + var poolHandle *[]byte + useBufferPool := !mem.IsBelowBufferPoolingThreshold(int(fh.Length)) + if useBufferPool { + poolHandle = f.pool.Get(int(fh.Length)) + buf = *poolHandle + defer func() { + if err != nil { + f.pool.Put(poolHandle) + } + }() + } else { + buf = make([]byte, int(fh.Length)) + } if fh.Flags.Has(http2.FlagDataPadded) { if fh.Length == 0 { return io.ErrUnexpectedEOF @@ -482,28 +495,34 @@ func (f *framer) readDataFrame(fh http2.FrameHeader, pool mem.BufferPool, df *pa // but it allows the rest of the payload to be read directly to the // start of the destination slice. This makes it easy to return the // original slice back to the buffer pool. - if _, err := io.ReadFull(f.reader, (*payload)[:1]); err != nil { + if _, err := io.ReadFull(f.reader, buf[:1]); err != nil { return err } - padSize := (*payload)[0] - *payload = (*payload)[:len(*payload)-1] - if _, err := io.ReadFull(f.reader, *payload); err != nil { + padSize := buf[0] + buf = buf[:len(buf)-1] + if _, err := io.ReadFull(f.reader, buf); err != nil { return err } - if int(padSize) > len(*payload) { + if int(padSize) > len(buf) { // If the length of the padding is greater than the // length of the frame payload, the recipient MUST // treat this as a connection error. // Filed: https://github.com/http2/http2-spec/issues/610 return fmt.Errorf("pad size larger than data payload") } - *payload = (*payload)[:len(*payload)-int(padSize)] - } else if _, err := io.ReadFull(f.reader, *payload); err != nil { + buf = buf[:len(buf)-int(padSize)] + } else if _, err := io.ReadFull(f.reader, buf); err != nil { return err } - df.FrameHeader = fh - df.data = mem.NewBuffer(payload, pool) + f.dataFrame.FrameHeader = fh + if useBufferPool { + // Update the handle to point to the (potentially re-sliced) buf. + *poolHandle = buf + f.dataFrame.data = mem.NewBuffer(poolHandle, f.pool) + } else { + f.dataFrame.data = mem.SliceBuffer(buf) + } return nil } From 5ec4a4eb851841fd2dc36c2cd53cc5780fb486b5 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Fri, 17 Oct 2025 00:42:05 +0530 Subject: [PATCH 05/11] bump x/net to golang.org/x/net@63d1a51 --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- gcp/observability/go.mod | 2 +- gcp/observability/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- interop/observability/go.mod | 2 +- interop/observability/go.sum | 4 ++-- interop/xds/go.mod | 2 +- interop/xds/go.sum | 4 ++-- security/advancedtls/examples/go.mod | 2 +- security/advancedtls/examples/go.sum | 4 ++-- security/advancedtls/go.mod | 2 +- security/advancedtls/go.sum | 4 ++-- stats/opencensus/go.mod | 2 +- stats/opencensus/go.sum | 4 ++-- 16 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 1ce603d05832..8272a93043ac 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -74,7 +74,7 @@ require ( go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 642f08f234f4..bdaa5815653b 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -4122,8 +4122,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/gcp/observability/go.mod b/gcp/observability/go.mod index 175d3be745aa..a2214f32d32b 100644 --- a/gcp/observability/go.mod +++ b/gcp/observability/go.mod @@ -51,7 +51,7 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect diff --git a/gcp/observability/go.sum b/gcp/observability/go.sum index a0fac0011fd9..e4037d61ac73 100644 --- a/gcp/observability/go.sum +++ b/gcp/observability/go.sum @@ -3810,8 +3810,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/go.mod b/go.mod index 282140722fdb..fd73ec7d9a94 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( go.opentelemetry.io/otel/sdk v1.38.0 go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 - golang.org/x/net v0.46.0 + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 golang.org/x/oauth2 v0.32.0 golang.org/x/sync v0.17.0 golang.org/x/sys v0.37.0 diff --git a/go.sum b/go.sum index 93e1044da556..4196237e1ff1 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= diff --git a/interop/observability/go.mod b/interop/observability/go.mod index 33a609634c11..963c9ee16c7a 100644 --- a/interop/observability/go.mod +++ b/interop/observability/go.mod @@ -50,7 +50,7 @@ require ( go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect diff --git a/interop/observability/go.sum b/interop/observability/go.sum index f146e0f9c1ba..493b7abefdd6 100644 --- a/interop/observability/go.sum +++ b/interop/observability/go.sum @@ -4092,8 +4092,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/interop/xds/go.mod b/interop/xds/go.mod index f88612750dc0..91766ca4b00b 100644 --- a/interop/xds/go.mod +++ b/interop/xds/go.mod @@ -38,7 +38,7 @@ require ( go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect diff --git a/interop/xds/go.sum b/interop/xds/go.sum index f4ac6ac72af1..d2d0cc57219e 100644 --- a/interop/xds/go.sum +++ b/interop/xds/go.sum @@ -83,8 +83,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= diff --git a/security/advancedtls/examples/go.mod b/security/advancedtls/examples/go.mod index 362458c29b69..5e89b35a7855 100644 --- a/security/advancedtls/examples/go.mod +++ b/security/advancedtls/examples/go.mod @@ -12,7 +12,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect diff --git a/security/advancedtls/examples/go.sum b/security/advancedtls/examples/go.sum index 4ca2f35546c8..b89e389616c5 100644 --- a/security/advancedtls/examples/go.sum +++ b/security/advancedtls/examples/go.sum @@ -32,8 +32,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= diff --git a/security/advancedtls/go.mod b/security/advancedtls/go.mod index 8f8234562392..58649f15a8a4 100644 --- a/security/advancedtls/go.mod +++ b/security/advancedtls/go.mod @@ -12,7 +12,7 @@ require ( require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect diff --git a/security/advancedtls/go.sum b/security/advancedtls/go.sum index 4ca2f35546c8..b89e389616c5 100644 --- a/security/advancedtls/go.sum +++ b/security/advancedtls/go.sum @@ -32,8 +32,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= diff --git a/stats/opencensus/go.mod b/stats/opencensus/go.mod index 280fda54ff16..9422581971c3 100644 --- a/stats/opencensus/go.mod +++ b/stats/opencensus/go.mod @@ -10,7 +10,7 @@ require ( require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - golang.org/x/net v0.46.0 // indirect + golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect diff --git a/stats/opencensus/go.sum b/stats/opencensus/go.sum index e292d4e53479..c121cb6bdf5e 100644 --- a/stats/opencensus/go.sum +++ b/stats/opencensus/go.sum @@ -3750,8 +3750,8 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= +golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 6820fee324dd5aa251a586b3ef4c4f00d43d584e Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Tue, 21 Oct 2025 14:14:07 +0530 Subject: [PATCH 06/11] fix error codes and interface types --- internal/transport/http_util.go | 14 +++++++------- internal/transport/http_util_test.go | 27 ++++++++++++++------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index f4159690a203..ba81f7ab4509 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -300,11 +300,11 @@ type bufWriter struct { buf []byte offset int batchSize int - conn io.ReadWriter + conn io.Writer err error } -func newBufWriter(conn io.ReadWriter, batchSize int, pool *sync.Pool) *bufWriter { +func newBufWriter(conn io.Writer, batchSize int, pool *sync.Pool) *bufWriter { w := &bufWriter{ batchSize: batchSize, conn: conn, @@ -466,7 +466,7 @@ func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { // field is 0x0, the recipient MUST respond with a // connection error (Section 5.4.1) of type // PROTOCOL_ERROR. - return fmt.Errorf("DATA frame with stream ID 0") + return http2.ConnectionError(http2.ErrCodeProtocol) } // Converting a *[]byte to a mem.BufferSlice incurs a heap allocation. This // conversion is performed by mem.NewBuffer. To avoid the extra allocation @@ -500,15 +500,15 @@ func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { } padSize := buf[0] buf = buf[:len(buf)-1] - if _, err := io.ReadFull(f.reader, buf); err != nil { - return err - } if int(padSize) > len(buf) { // If the length of the padding is greater than the // length of the frame payload, the recipient MUST // treat this as a connection error. // Filed: https://github.com/http2/http2-spec/issues/610 - return fmt.Errorf("pad size larger than data payload") + return http2.ConnectionError(http2.ErrCodeProtocol) + } + if _, err := io.ReadFull(f.reader, buf); err != nil { + return err } buf = buf[:len(buf)-int(padSize)] } else if _, err := io.ReadFull(f.reader, buf); err != nil { diff --git a/internal/transport/http_util_test.go b/internal/transport/http_util_test.go index 69e631d8b215..901a7253398f 100644 --- a/internal/transport/http_util_test.go +++ b/internal/transport/http_util_test.go @@ -29,6 +29,7 @@ import ( "testing" "time" + "golang.org/x/net/http2" "google.golang.org/grpc/mem" ) @@ -301,39 +302,39 @@ func BenchmarkEncodeGrpcMessage(b *testing.B) { func (s) TestFrame_ReadDataFrame(t *testing.T) { tests := []struct { - name string - w func(fr *framer) error - wantData []byte - wantErr bool + name string + writeFrames func(fr *framer) error + wantData []byte + wantErr error }{ { name: "good_padded", - w: func(fr *framer) error { + writeFrames: func(fr *framer) error { return fr.fr.WriteDataPadded(1, false, []byte("foo"), []byte{0, 0}) }, wantData: []byte("foo"), }, { name: "good_unpadded", - w: func(fr *framer) error { + writeFrames: func(fr *framer) error { return fr.fr.WriteData(1, false, []byte("foo")) }, wantData: []byte("foo"), }, { name: "padded_zero_data_some_padding", - w: func(fr *framer) error { + writeFrames: func(fr *framer) error { return fr.fr.WriteDataPadded(1, false, []byte{}, []byte{0, 0}) }, wantData: []byte{}, }, { name: "stream_id_0", - w: func(fr *framer) error { + writeFrames: func(fr *framer) error { fr.fr.AllowIllegalWrites = true return fr.fr.WriteData(0, false, []byte("foo")) }, - wantErr: true, + wantErr: http2.ConnectionError(http2.ErrCodeProtocol), }, } @@ -341,7 +342,7 @@ func (s) TestFrame_ReadDataFrame(t *testing.T) { t.Run(tt.name, func(t *testing.T) { fr, _ := testFramer() // Write the frame using the provided function. - writeErr := tt.w(fr) + writeErr := tt.writeFrames(fr) if writeErr != nil { t.Fatalf("tt.w() returned unexpected error: %v", writeErr) } @@ -349,10 +350,10 @@ func (s) TestFrame_ReadDataFrame(t *testing.T) { // Read the frame back. f, err := fr.readFrame() - if gotErr := err != nil; gotErr != tt.wantErr { - t.Fatalf("ReadFrame() err = %v; want %t", err, tt.wantErr) + if err != tt.wantErr { + t.Fatalf("ReadFrame() err = %v; want %v", err, tt.wantErr) } - if tt.wantErr { + if tt.wantErr != nil { return } From 00586ba7f105bc2ed12f4302c95cc333bc3aea47 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Tue, 21 Oct 2025 15:09:55 +0530 Subject: [PATCH 07/11] Improve testing of error cases --- internal/transport/http2_client.go | 2 +- internal/transport/http_util.go | 22 ++++- internal/transport/http_util_test.go | 126 ++++++++++++++++++--------- 3 files changed, 105 insertions(+), 45 deletions(-) diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index cff2c616ad21..6394697032fa 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -1676,7 +1676,7 @@ func (t *http2Client) reader(errCh chan<- error) { if s != nil { // use error detail to provide better err message code := http2ErrConvTab[se.Code] - errorDetail := t.framer.fr.ErrorDetail() + errorDetail := t.framer.errorDetail() var msg string if errorDetail != nil { msg = errorDetail.Error() diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index ba81f7ab4509..e4257b5f5c3e 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -404,6 +404,7 @@ type framer struct { // Cached data frame to avoid heap allocations. dataFrame parsedDataFrame pool mem.BufferPool + errDetail error } var writeBufferPoolMap = make(map[int]*sync.Pool) @@ -446,8 +447,10 @@ func newFramer(conn io.ReadWriter, writeBufferSize, readBufferSize int, sharedWr // readFrame reads a single frame. The returned Frame is only valid // until the next call to readFrame. func (f *framer) readFrame() (any, error) { + f.errDetail = nil fh, err := f.fr.ReadFrameHeader() if err != nil { + f.errDetail = f.fr.ErrorDetail() return nil, err } // Read the data frame directly from the underlying io.Reader to avoid @@ -456,7 +459,22 @@ func (f *framer) readFrame() (any, error) { err = f.readDataFrame(fh) return &f.dataFrame, err } - return f.fr.ReadFrameForHeader(fh) + fr, err := f.fr.ReadFrameForHeader(fh) + if err != nil { + f.errDetail = f.fr.ErrorDetail() + return nil, err + } + return fr, err +} + +// errorDetail returns a more detailed error of the last error +// returned by framer.readFrame. For instance, if readFrame +// returns a StreamError with code PROTOCOL_ERROR, errorDetail +// will say exactly what was invalid. errorDetail is not guaranteed +// to return a non-nil value. +// errorDetail is reset after the next call to readFrame. +func (f *framer) errorDetail() error { + return f.errDetail } func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { @@ -466,6 +484,7 @@ func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { // field is 0x0, the recipient MUST respond with a // connection error (Section 5.4.1) of type // PROTOCOL_ERROR. + f.errDetail = errors.New("DATA frame with stream ID 0") return http2.ConnectionError(http2.ErrCodeProtocol) } // Converting a *[]byte to a mem.BufferSlice incurs a heap allocation. This @@ -505,6 +524,7 @@ func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { // length of the frame payload, the recipient MUST // treat this as a connection error. // Filed: https://github.com/http2/http2-spec/issues/610 + f.errDetail = errors.New("pad size larger than data payload") return http2.ConnectionError(http2.ErrCodeProtocol) } if _, err := io.ReadFull(f.reader, buf); err != nil { diff --git a/internal/transport/http_util_test.go b/internal/transport/http_util_test.go index 901a7253398f..bda6d777a001 100644 --- a/internal/transport/http_util_test.go +++ b/internal/transport/http_util_test.go @@ -26,6 +26,7 @@ import ( "math" "net" "reflect" + "strings" "testing" "time" @@ -300,76 +301,115 @@ func BenchmarkEncodeGrpcMessage(b *testing.B) { } } -func (s) TestFrame_ReadDataFrame(t *testing.T) { +func buildDataFrame(h http2.FrameHeader, payload []byte) []byte { + buf := new(bytes.Buffer) + buf.Write([]byte{ + byte(h.Length >> 16), + byte(h.Length >> 8), + byte(h.Length), + byte(h.Type), + byte(h.Flags), + byte(h.StreamID >> 24), + byte(h.StreamID >> 16), + byte(h.StreamID >> 8), + byte(h.StreamID), + }) + buf.Write(payload) + return buf.Bytes() +} + +func (s) TestFramer_ParseDataFrame(t *testing.T) { tests := []struct { - name string - writeFrames func(fr *framer) error - wantData []byte - wantErr error + name string + wire []byte // from frame header onward + wantData []byte + wantErr error + wantErrDetailSubstr string }{ { name: "good_padded", - writeFrames: func(fr *framer) error { - return fr.fr.WriteDataPadded(1, false, []byte("foo"), []byte{0, 0}) - }, + wire: buildDataFrame(http2.FrameHeader{ + Type: http2.FrameData, Length: 6, StreamID: 1, Flags: http2.FlagDataPadded, + }, []byte{ + 2, // pad length + 'f', 'o', 'o', // data + 0, 0, // padding + }), wantData: []byte("foo"), }, { name: "good_unpadded", - writeFrames: func(fr *framer) error { - return fr.fr.WriteData(1, false, []byte("foo")) - }, + wire: buildDataFrame(http2.FrameHeader{ + Type: http2.FrameData, Length: 3, StreamID: 1, Flags: 0, + }, []byte("foo")), wantData: []byte("foo"), }, + { + name: "stream_id_0", + wire: buildDataFrame(http2.FrameHeader{ + Type: http2.FrameData, Length: 1, StreamID: 0, Flags: 0, + }, []byte{0}), + wantErr: http2.ConnectionError(http2.ErrCodeProtocol), + wantErrDetailSubstr: "DATA frame with stream ID 0", + }, + { + name: "pad_size_bigger_than_payload", + wire: buildDataFrame(http2.FrameHeader{ + Type: http2.FrameData, Length: 4, StreamID: 1, Flags: http2.FlagDataPadded, + }, []byte{ + 4, // pad length of 4 + 'f', 'o', // data 'fo' is 2 bytes + 0, // padding 0 is 1 byte. + }), // pad length 4 but only 3 bytes for data+padding available in payload after pad length byte + wantErr: http2.ConnectionError(http2.ErrCodeProtocol), + wantErrDetailSubstr: "pad size larger than data payload", + }, { name: "padded_zero_data_some_padding", - writeFrames: func(fr *framer) error { - return fr.fr.WriteDataPadded(1, false, []byte{}, []byte{0, 0}) - }, + wire: buildDataFrame(http2.FrameHeader{ + Type: http2.FrameData, Length: 3, StreamID: 1, Flags: http2.FlagDataPadded, + }, []byte{ + 2, // pad length 2 + 0, 0, // padding + }), wantData: []byte{}, }, { - name: "stream_id_0", - writeFrames: func(fr *framer) error { - fr.fr.AllowIllegalWrites = true - return fr.fr.WriteData(0, false, []byte("foo")) - }, - wantErr: http2.ConnectionError(http2.ErrCodeProtocol), + name: "padded_short_payload_reading_pad_flag", + wire: buildDataFrame(http2.FrameHeader{ + Type: http2.FrameData, Length: 0, StreamID: 1, Flags: http2.FlagDataPadded, + }, []byte{}), + wantErr: io.ErrUnexpectedEOF, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fr, _ := testFramer() - // Write the frame using the provided function. - writeErr := tt.writeFrames(fr) - if writeErr != nil { - t.Fatalf("tt.w() returned unexpected error: %v", writeErr) - } - fr.writer.Flush() - - // Read the frame back. + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + fr := newFramer(bytes.NewBuffer(tc.wire), defaultWriteBufSize, defaultReadBufSize, false, defaultClientMaxHeaderListSize, mem.DefaultBufferPool()) f, err := fr.readFrame() - if err != tt.wantErr { - t.Fatalf("ReadFrame() err = %v; want %v", err, tt.wantErr) + + if err != tc.wantErr { + t.Fatalf("readFrame() returned unexpected error: %v, want %v", err, tc.wantErr) } - if tt.wantErr != nil { - return + gotErrDetailStr := "" + if fr.errDetail != nil { + gotErrDetailStr = fr.errDetail.Error() + } + if !strings.Contains(gotErrDetailStr, tc.wantErrDetailSubstr) { + t.Fatalf("errorDetail() returned unexpected error string: %q, want substring %q", gotErrDetailStr, tc.wantErrDetailSubstr) } + if tc.wantErr != nil { + return + } df, ok := f.(*parsedDataFrame) if !ok { - t.Fatalf("ReadFrame() returned %T, want *parsedDataFrame", f) + t.Fatalf("readFrame() returned %T, want *parsedDataFrame", f) } - if gotData := df.data.ReadOnlyData(); !bytes.Equal(gotData, tt.wantData) { - t.Fatalf("parsedDataFrame.Data() = %q, want %q", gotData, tt.wantData) + if gotData := df.data.ReadOnlyData(); !bytes.Equal(gotData, tc.wantData) { + t.Fatalf("parsedDataFrame.Data() = %q, want %q", gotData, tc.wantData) } df.data.Free() }) } } - -func testFramer() (*framer, *bytes.Buffer) { - buf := new(bytes.Buffer) - return newFramer(buf, defaultWriteBufSize, defaultReadBufSize, false, defaultClientMaxHeaderListSize, mem.DefaultBufferPool()), buf -} From 8ef2d03f78c04c7cdd1ac3018126b5858e028ae3 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Tue, 28 Oct 2025 21:26:28 +0530 Subject: [PATCH 08/11] doc comments --- internal/transport/http_util.go | 9 ++++----- mem/buffer_pool.go | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index e4257b5f5c3e..76e6f21bc2f1 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -398,11 +398,10 @@ func (df *parsedDataFrame) StreamEnded() bool { } type framer struct { - writer *bufWriter - fr *http2.Framer - reader io.Reader - // Cached data frame to avoid heap allocations. - dataFrame parsedDataFrame + writer *bufWriter + fr *http2.Framer + reader io.Reader + dataFrame parsedDataFrame // Cached data frame to avoid heap allocations. pool mem.BufferPool errDetail error } diff --git a/mem/buffer_pool.go b/mem/buffer_pool.go index c37c58c0233e..cd7f88615ade 100644 --- a/mem/buffer_pool.go +++ b/mem/buffer_pool.go @@ -32,6 +32,9 @@ type BufferPool interface { Get(length int) *[]byte // Put returns a buffer to the pool. + // + // The returned pointer must hold a prefix of the buffer obtained via [Get] + // to ensure the buffer's entire capacity can be re-used. Put(*[]byte) } From a121b1550cc1df610a2325ebc474dd6b5343fd2e Mon Sep 17 00:00:00 2001 From: Arjan Singh Bal <46515553+arjan-bal@users.noreply.github.com> Date: Fri, 31 Oct 2025 11:13:43 +0530 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Doug Fawley --- mem/buffer_pool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mem/buffer_pool.go b/mem/buffer_pool.go index cd7f88615ade..939a81870f8f 100644 --- a/mem/buffer_pool.go +++ b/mem/buffer_pool.go @@ -33,7 +33,7 @@ type BufferPool interface { // Put returns a buffer to the pool. // - // The returned pointer must hold a prefix of the buffer obtained via [Get] + // The provided pointer must hold a prefix of the buffer obtained via [Get] // to ensure the buffer's entire capacity can be re-used. Put(*[]byte) } From e7b6d3584d9594f80a407062e8624e22f8b68200 Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Fri, 31 Oct 2025 11:16:59 +0530 Subject: [PATCH 10/11] Fix comment --- internal/transport/http_util.go | 5 ++--- mem/buffer_pool.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index 76e6f21bc2f1..0ba6c7e79941 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -486,10 +486,9 @@ func (f *framer) readDataFrame(fh http2.FrameHeader) (err error) { f.errDetail = errors.New("DATA frame with stream ID 0") return http2.ConnectionError(http2.ErrCodeProtocol) } - // Converting a *[]byte to a mem.BufferSlice incurs a heap allocation. This + // Converting a *[]byte to a mem.SliceBuffer incurs a heap allocation. This // conversion is performed by mem.NewBuffer. To avoid the extra allocation - // a []byte is allocated directly if required and casted to a - // mem.BufferSlice. + // a []byte is allocated directly if required and cast to a mem.SliceBuffer. var buf []byte // poolHandle is the pointer returned by the buffer pool (if it's used.). var poolHandle *[]byte diff --git a/mem/buffer_pool.go b/mem/buffer_pool.go index 939a81870f8f..d455033ec968 100644 --- a/mem/buffer_pool.go +++ b/mem/buffer_pool.go @@ -33,8 +33,8 @@ type BufferPool interface { // Put returns a buffer to the pool. // - // The provided pointer must hold a prefix of the buffer obtained via [Get] - // to ensure the buffer's entire capacity can be re-used. + // The provided pointer must hold a prefix of the buffer obtained via + // BufferPool.Get to ensure the buffer's entire capacity can be re-used. Put(*[]byte) } From f0e9b133dc1cb06ac41aee3e4a05ac551752c6bc Mon Sep 17 00:00:00 2001 From: Arjan Bal Date: Fri, 31 Oct 2025 11:49:15 +0530 Subject: [PATCH 11/11] Fix merge conflicts --- examples/go.sum | 1 + interop/observability/go.sum | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/go.sum b/examples/go.sum index 8855d98dd72e..0235a2850417 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -4126,6 +4126,7 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/interop/observability/go.sum b/interop/observability/go.sum index c4bf65abb5bf..4782e3921844 100644 --- a/interop/observability/go.sum +++ b/interop/observability/go.sum @@ -4096,6 +4096,7 @@ golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=