From 0fb892f066d345b8c77c682013eccad4dec2aa52 Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Wed, 13 Aug 2025 09:50:47 -0700 Subject: [PATCH 1/3] deps: bump go version to 1.24 It's generally useful to have new-and-improved Go. One specific useful feature is `b.Loop()`, which makes benchmarking easier. --- cmd/protoc-gen-go-grpc/go.mod | 2 +- examples/go.mod | 2 +- gcp/observability/go.mod | 2 +- go.mod | 2 +- interop/observability/go.mod | 2 +- interop/xds/go.mod | 2 +- scripts/vet.sh | 2 +- security/advancedtls/examples/go.mod | 2 +- security/advancedtls/go.mod | 2 +- stats/opencensus/go.mod | 2 +- test/tools/go.mod | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/protoc-gen-go-grpc/go.mod b/cmd/protoc-gen-go-grpc/go.mod index 88ce30d67b47..5182dd832504 100644 --- a/cmd/protoc-gen-go-grpc/go.mod +++ b/cmd/protoc-gen-go-grpc/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/cmd/protoc-gen-go-grpc -go 1.23.0 +go 1.24.0 require ( google.golang.org/grpc v1.70.0 diff --git a/examples/go.mod b/examples/go.mod index b9a0905e89e7..9f9aef89b7b6 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/examples -go 1.23.0 +go 1.24.0 require ( github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 diff --git a/gcp/observability/go.mod b/gcp/observability/go.mod index d766a4ecd93f..2172f53afdaf 100644 --- a/gcp/observability/go.mod +++ b/gcp/observability/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/gcp/observability -go 1.23.0 +go 1.24.0 require ( cloud.google.com/go/logging v1.13.0 diff --git a/go.mod b/go.mod index 2ded5df83a4f..a9926f6d9757 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc -go 1.23.0 +go 1.24.0 require ( github.com/cespare/xxhash/v2 v2.3.0 diff --git a/interop/observability/go.mod b/interop/observability/go.mod index 48f04f389e0b..f9f70e0687ad 100644 --- a/interop/observability/go.mod +++ b/interop/observability/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/interop/observability -go 1.23.0 +go 1.24.0 require ( google.golang.org/grpc v1.73.0 diff --git a/interop/xds/go.mod b/interop/xds/go.mod index 0f67f0d3ca5b..8b4a31d6f21a 100644 --- a/interop/xds/go.mod +++ b/interop/xds/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/interop/xds -go 1.23.0 +go 1.24.0 replace google.golang.org/grpc => ../.. diff --git a/scripts/vet.sh b/scripts/vet.sh index 18c4085fce54..402e589c5fe2 100755 --- a/scripts/vet.sh +++ b/scripts/vet.sh @@ -97,7 +97,7 @@ for MOD_FILE in $(find . -name 'go.mod'); do gofmt -s -d -l . 2>&1 | fail_on_output goimports -l . 2>&1 | not grep -vE "\.pb\.go" - go mod tidy -compat=1.23 + go mod tidy -compat=1.24 git status --porcelain 2>&1 | fail_on_output || \ (git status; git --no-pager diff; exit 1) diff --git a/security/advancedtls/examples/go.mod b/security/advancedtls/examples/go.mod index b159aa5a7353..21efee72221d 100644 --- a/security/advancedtls/examples/go.mod +++ b/security/advancedtls/examples/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/security/advancedtls/examples -go 1.23.0 +go 1.24.0 require ( google.golang.org/grpc v1.73.0 diff --git a/security/advancedtls/go.mod b/security/advancedtls/go.mod index 89ee48feb77b..5321cc579d21 100644 --- a/security/advancedtls/go.mod +++ b/security/advancedtls/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/security/advancedtls -go 1.23.0 +go 1.24.0 require ( github.com/google/go-cmp v0.7.0 diff --git a/stats/opencensus/go.mod b/stats/opencensus/go.mod index 8b258ddc4c13..8c56d9b23272 100644 --- a/stats/opencensus/go.mod +++ b/stats/opencensus/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/stats/opencensus -go 1.23.0 +go 1.24.0 require ( github.com/google/go-cmp v0.7.0 diff --git a/test/tools/go.mod b/test/tools/go.mod index 4c8f58edac60..7c8341315c69 100644 --- a/test/tools/go.mod +++ b/test/tools/go.mod @@ -1,6 +1,6 @@ module google.golang.org/grpc/test/tools -go 1.23.0 +go 1.24.0 require ( github.com/client9/misspell v0.3.4 From dc8a0ca21670485fcd22774f9528f020dd499e1d Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Wed, 6 Aug 2025 09:30:39 -0700 Subject: [PATCH 2/3] alts: move ParseFramedMsg out of common It's only called in one place, and is effectively a method on conn. Part of #8510. --- credentials/alts/internal/conn/common.go | 32 --------------------- credentials/alts/internal/conn/record.go | 36 ++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/credentials/alts/internal/conn/common.go b/credentials/alts/internal/conn/common.go index 46617132a456..d4c3ab7989f7 100644 --- a/credentials/alts/internal/conn/common.go +++ b/credentials/alts/internal/conn/common.go @@ -19,9 +19,7 @@ package conn import ( - "encoding/binary" "errors" - "fmt" ) const ( @@ -48,33 +46,3 @@ func SliceForAppend(in []byte, n int) (head, tail []byte) { tail = head[len(in):] return head, tail } - -// ParseFramedMsg parse the provided buffer and returns a frame of the format -// msgLength+msg and any remaining bytes in that buffer. -func ParseFramedMsg(b []byte, maxLen uint32) ([]byte, []byte, error) { - // If the size field is not complete, return the provided buffer as - // remaining buffer. - length, sufficientBytes := parseMessageLength(b) - if !sufficientBytes { - return nil, b, nil - } - if length > maxLen { - return nil, nil, fmt.Errorf("received the frame length %d larger than the limit %d", length, maxLen) - } - if len(b) < int(length)+4 { // account for the first 4 msg length bytes. - // Frame is not complete yet. - return nil, b, nil - } - return b[:MsgLenFieldSize+length], b[MsgLenFieldSize+length:], nil -} - -// parseMessageLength returns the message length based on frame header. It also -// returns a boolean indicating if the buffer contains sufficient bytes to parse -// the length header. If there are insufficient bytes, (0, false) is returned. -func parseMessageLength(b []byte) (uint32, bool) { - if len(b) < MsgLenFieldSize { - return 0, false - } - msgLenField := b[:MsgLenFieldSize] - return binary.LittleEndian.Uint32(msgLenField), true -} diff --git a/credentials/alts/internal/conn/record.go b/credentials/alts/internal/conn/record.go index f9d2646d4b22..45d09f1307fc 100644 --- a/credentials/alts/internal/conn/record.go +++ b/credentials/alts/internal/conn/record.go @@ -144,7 +144,7 @@ func NewConn(c net.Conn, side core.Side, recordProtocol string, key []byte, prot func (p *conn) Read(b []byte) (n int, err error) { if len(p.buf) == 0 { var framedMsg []byte - framedMsg, p.nextFrame, err = ParseFramedMsg(p.nextFrame, altsRecordLengthLimit) + framedMsg, err = p.parseFramedMsg(p.nextFrame, altsRecordLengthLimit) if err != nil { return n, err } @@ -184,7 +184,7 @@ func (p *conn) Read(b []byte) (n int, err error) { return 0, err } p.protected = p.protected[:len(p.protected)+n] - framedMsg, p.nextFrame, err = ParseFramedMsg(p.protected, altsRecordLengthLimit) + framedMsg, err = p.parseFramedMsg(p.protected, altsRecordLengthLimit) if err != nil { return 0, err } @@ -225,6 +225,38 @@ func (p *conn) Read(b []byte) (n int, err error) { return n, nil } +// parseFramedMsg parses the provided buffer and returns a frame of the format +// msgLength+msg iff a full frame is available. +func (p *conn) parseFramedMsg(b []byte, maxLen uint32) ([]byte, error) { + // If the size field is not complete, return the provided buffer as + // remaining buffer. + p.nextFrame = b + length, sufficientBytes := parseMessageLength(b) + if !sufficientBytes { + return nil, nil + } + if length > maxLen { + return nil, fmt.Errorf("received the frame length %d larger than the limit %d", length, maxLen) + } + if len(b) < int(length)+4 { // account for the first 4 msg length bytes. + // Frame is not complete yet. + return nil, nil + } + p.nextFrame = b[MsgLenFieldSize+length:] + return b[:MsgLenFieldSize+length], nil +} + +// parseMessageLength returns the message length based on frame header. It also +// returns a boolean indicating if the buffer contains sufficient bytes to parse +// the length header. If there are insufficient bytes, (0, false) is returned. +func parseMessageLength(b []byte) (uint32, bool) { + if len(b) < MsgLenFieldSize { + return 0, false + } + msgLenField := b[:MsgLenFieldSize] + return binary.LittleEndian.Uint32(msgLenField), true +} + // Write encrypts, frames, and writes bytes from b to the underlying connection. func (p *conn) Write(b []byte) (n int, err error) { n = len(b) From 69745343c6c81058651ca7ea3d6e1193e6493ccf Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Sat, 9 Aug 2025 22:41:23 -0700 Subject: [PATCH 3/3] alts: increase write record size max to 1MB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increases large write speed by 9.62% per BenchmarkLargeMessage. Detailed benchmarking numbers below. Rather than use different sizes for the maximum read record, write record, and write buffer, just use 1MB for all of them. Using larger records reduces the amount of payload splitting and the number of syscalls made by ALTS. Part of #8510. SO_RCVLOWAT and TCP receive zerocopy are only effective with larger payloads, and so ALTS can't be limiting payload sizes to 4 KiB. SO_RCVLOWAT and zerocopy are on the receive side, but for benchmarking purposes we need ALTS to send large messages. Benchmarks: $ benchstat large_msg_old.txt large_msg.txt goos: linux goarch: amd64 pkg: google.golang.org/grpc/credentials/alts/internal/conn cpu: AMD Ryzen Threadripper PRO 3945WX 12-Cores │ large_msg_old.txt │ large_msg.txt │ │ sec/op │ sec/op vs base │ LargeMessage-12 68.88m ± 1% 62.25m ± 0% -9.62% (p=0.002 n=6) --- credentials/alts/internal/conn/record.go | 18 ++++++------------ credentials/alts/internal/conn/record_test.go | 6 +++--- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/credentials/alts/internal/conn/record.go b/credentials/alts/internal/conn/record.go index 45d09f1307fc..aa893d91c354 100644 --- a/credentials/alts/internal/conn/record.go +++ b/credentials/alts/internal/conn/record.go @@ -56,17 +56,12 @@ const ( MsgLenFieldSize = 4 // The byte size of the message type field of a framed message. msgTypeFieldSize = 4 - // The bytes size limit for a ALTS record message. + // The bytes size limit for an ALTS record message. altsRecordLengthLimit = 1024 * 1024 // 1 MiB - // The default bytes size of a ALTS record message. - altsRecordDefaultLength = 4 * 1024 // 4KiB // Message type value included in ALTS record framing. altsRecordMsgType = uint32(0x06) // The initial write buffer size. altsWriteBufferInitialSize = 32 * 1024 // 32KiB - // The maximum write buffer size. This *must* be multiple of - // altsRecordDefaultLength. - altsWriteBufferMaxSize = 512 * 1024 // 512KiB // The initial buffer used to read from the network. altsReadBufferInitialSize = 32 * 1024 // 32KiB ) @@ -116,7 +111,7 @@ func NewConn(c net.Conn, side core.Side, recordProtocol string, key []byte, prot return nil, fmt.Errorf("protocol %q: %v", recordProtocol, err) } overhead := MsgLenFieldSize + msgTypeFieldSize + crypto.EncryptionOverhead() - payloadLengthLimit := altsRecordDefaultLength - overhead + payloadLengthLimit := altsRecordLengthLimit - overhead // We pre-allocate protected to be of size 32KB during initialization. // We increase the size of the buffer by the required amount if it can't // hold a complete encrypted record. @@ -265,10 +260,9 @@ func (p *conn) Write(b []byte) (n int, err error) { size := len(b) + numOfFrames*p.overhead // If writeBuf is too small, increase its size up to the maximum size. partialBSize := len(b) - if size > altsWriteBufferMaxSize { - size = altsWriteBufferMaxSize - const numOfFramesInMaxWriteBuf = altsWriteBufferMaxSize / altsRecordDefaultLength - partialBSize = numOfFramesInMaxWriteBuf * p.payloadLengthLimit + if size > altsRecordLengthLimit { + size = altsRecordLengthLimit + partialBSize = p.payloadLengthLimit } if len(p.writeBuf) < size { p.writeBuf = make([]byte, size) @@ -314,7 +308,7 @@ func (p *conn) Write(b []byte) (n int, err error) { // written. This means we need to remove header, // encryption overheads, and any partially-written // frame data. - numOfWrittenFrames := int(math.Floor(float64(nn) / float64(altsRecordDefaultLength))) + numOfWrittenFrames := int(math.Floor(float64(nn) / float64(altsRecordLengthLimit))) return partialBStart + numOfWrittenFrames*p.payloadLengthLimit, err } } diff --git a/credentials/alts/internal/conn/record_test.go b/credentials/alts/internal/conn/record_test.go index e4992489a189..477dccc22907 100644 --- a/credentials/alts/internal/conn/record_test.go +++ b/credentials/alts/internal/conn/record_test.go @@ -168,8 +168,8 @@ func (s) TestSmallReadBuffer(t *testing.T) { func testLargeMsg(t *testing.T, rp string) { clientConn, serverConn := newConnPair(rp, nil, nil) // msgLen is such that the length in the framing is larger than the - // default size of one frame. - msgLen := altsRecordDefaultLength - msgTypeFieldSize - clientConn.crypto.EncryptionOverhead() + 1 + // max size of one frame. + msgLen := altsRecordLengthLimit - msgTypeFieldSize - clientConn.crypto.EncryptionOverhead() + 1 msg := make([]byte, msgLen) if n, err := clientConn.Write(msg); n != len(msg) || err != nil { t.Fatalf("Write() = %v, %v; want %v, ", n, err, len(msg)) @@ -292,7 +292,7 @@ func testWriteLargeData(t *testing.T, rp string) { clientConn, serverConn := newConnPair(rp, nil, nil) // Message size is intentionally chosen to not be multiple of // payloadLengthLimit. - msgSize := altsWriteBufferMaxSize + (100 * 1024) + msgSize := altsRecordLengthLimit + (100 * 1024) clientMsg := make([]byte, msgSize) for i := 0; i < msgSize; i++ { clientMsg[i] = 0xAA