Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grpc: fix receiving empty messages when compression is enabled and maxReceiveMessageSize is MaxInt #7753 #7918

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

vinothkumarr227
Copy link

@vinothkumarr227 vinothkumarr227 commented Dec 10, 2024

Fixes #4552

RELEASE NOTES:

  • grpc: fix receiving empty messages when compression is enabled and maxReceiveMessageSize is maxInt

@arjan-bal arjan-bal self-requested a review December 11, 2024 11:09
@arjan-bal arjan-bal self-assigned this Dec 11, 2024
@arjan-bal arjan-bal added Area: RPC Features Includes Compression, Encoding, Attributes/Metadata, Interceptors. Type: Bug and removed Area: RPC Features Includes Compression, Encoding, Attributes/Metadata, Interceptors. labels Dec 11, 2024
@arjan-bal arjan-bal added this to the 1.70 Release milestone Dec 11, 2024
Copy link

codecov bot commented Dec 12, 2024

Codecov Report

Attention: Patch coverage is 95.83333% with 1 line in your changes missing coverage. Please review.

Project coverage is 82.13%. Comparing base (e8055ea) to head (0907f9d).
Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
rpc_util.go 95.83% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #7918      +/-   ##
==========================================
+ Coverage   82.08%   82.13%   +0.04%     
==========================================
  Files         379      381       +2     
  Lines       38261    38544     +283     
==========================================
+ Hits        31406    31657     +251     
- Misses       5551     5576      +25     
- Partials     1304     1311       +7     
Files with missing lines Coverage Δ
rpc_util.go 81.32% <95.83%> (+2.01%) ⬆️

... and 26 files with indirect coverage changes

rpc_util.go Outdated
if err != nil {
out.Free()
return nil, 0, err
}
if err = checkReceiveMessageOverflow(int64(out.Len()), int64(maxReceiveMessageSize), dcReader); err != nil {
return nil, out.Len() + 1, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding 1 to out.Len() can result in an overflow on 32 bit systems. The following part of the function seems strange to me:

Optionally, if data will be over maxReceiveMessageSize, just return the size

I suggest making the following change to avoid this:

  1. Declare a global error for indicating that the max receive size is exceeded:
var	errMaxMessageSizeExceeded = errors.New("max message size exceeded")
  1. When the check here fails, nil, 0, errMaxMessageSizeExceeded. Update the godoc to mention the same.
  2. In the caller, i.e. recvAndDecompress, instead of checking if size > maxReceiveMessageSize, check if err == errMaxMessageSizeExceeded. Also update the error message to not mention the actual size, because we didn't read the entire message anyways: grpc: received message after decompression larger than max %d.

This ensures we're using the returned error to indicate the failure instead of relying on special values of the bytes read count.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let the reviewer resolve comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not attempt to add out.Len() + 1, instead return errMaxMessageSizeExceeded to signal the same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, in case of overflow i think we are still reading the partial data so we need to return the length upto which we have read?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, the logic should be read till maxMessageSize -1 first and then check if we still have bytes to read by reading one byte and if we overflow, throw the error along with lenght of how much we have read

rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated Show resolved Hide resolved
rpc_util.go Outdated
if err != nil {
out.Free()
return nil, 0, err
}
if err = checkReceiveMessageOverflow(int64(out.Len()), int64(maxReceiveMessageSize), dcReader); err != nil {
return nil, out.Len() + 1, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let the reviewer resolve comments.

rpc_util.go Outdated
if err != nil {
out.Free()
return nil, 0, err
}
if err = checkReceiveMessageOverflow(int64(out.Len()), int64(maxReceiveMessageSize), dcReader); err != nil {
return nil, out.Len() + 1, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not attempt to add out.Len() + 1, instead return errMaxMessageSizeExceeded to signal the same.

rpc_util.go Outdated Show resolved Hide resolved
rpc_util_test.go Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
@vinothkumarr227 vinothkumarr227 force-pushed the 4552-max-receive-message-size-decompression branch from 9d80990 to 3cf9054 Compare December 18, 2024 07:44
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Outdated Show resolved Hide resolved
rpc_util_test.go Show resolved Hide resolved
rpc_util_test.go Outdated
wantErr: nil,
},
{
name: "Handles large buffer size (MaxInt)",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use special characters (like (, )) in the subtest names. Subtest names are used in test filters and go special characters will be escaped. It is not immediately clear what the escaped name would be in such cases.

rpc_util_test.go Outdated
// premature data end. It ensures that the function behaves correctly with different
// inputs, buffer sizes, and error conditions, using the "gzip" compressor for testing.
func (s) TestDecompress(t *testing.T) {
c := encoding.GetCompressor("gzip")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a longer name, e.g: compressor. Then length of a variable name should be proportional to it's scope. If a variable is accessed after 20 lines or so, it should have a longer name to avoid confusions.

rpc_util_test.go Outdated
}

if !cmp.Equal(tc.want, output.Materialize()) {
t.Fatalf("decompress() output mismatch: Got = %v, Want = %v", output.Materialize(), tc.want)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small G in got, small Winwant`.

rpc_util.go Outdated
}

// doesReceiveMessageOverflow checks if the number of bytes read from the stream
// exceeds the maximum receive message size allowed by the client. If the `readBytes`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: exceed instead of exceeds.

allowed by the client should be allowed by the receiver, the receiver could be the client or the server.

rpc_util.go Show resolved Hide resolved
rpc_util_test.go Outdated
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
output, err := decompress(compressor, tc.input, tc.maxReceiveMessageSize, mem.DefaultBufferPool())

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nix new line

rpc_util_test.go Outdated
if !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) {
t.Fatalf("decompress() error = %v, wantErr = %v", err, tc.wantErr)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nix new line

rpc_util_test.go Outdated
output, err := decompress(compressor, tc.input, tc.maxReceiveMessageSize, mem.DefaultBufferPool())

if !cmp.Equal(err, tc.wantErr, cmpopts.EquateErrors()) {
t.Fatalf("decompress() error = %v, wantErr = %v", err, tc.wantErr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.Fatalf("decompress() err = %v, wantErr = %v", err, tc.wantErr) or .Fatalf("decompress() error = %v, want error = %v", err, tc.wantErr)

rpc_util_test.go Outdated
},
{
name: "Handles large buffer size MaxInt",
input: mustCompress(t, []byte("large message")),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

large message is confusing. The bug which we are solving returns nil irrespective of what message size is if maxReceiveMessageSize is math.MaxInt. The test can be just Decompresses successfully with maxReceiveMessageSize MaxInt. Basically it can be similar to first test case except the maxReceiveMessageSize is MaxInt.

The other test case where you are actually going to overflow by providing a message larger than MaxInt is hard to write as unit test

rpc_util_test.go Outdated
return mem.BufferSlice{mem.NewBuffer(&compressedData, nil)}
}

// TestDecompress tests the decompress function with various scenarios, including
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestDecompress tests the decompress function behaves correctly for following scenarios

  • decompress successfully when message is <= maxReceiveMessageSize
  • errors when message > maxReceiveMessageSize
  • decompress successfully when maxReceiveMessageSize is MaxInt

rpc_util_test.go Outdated
@@ -294,3 +298,75 @@ func BenchmarkGZIPCompressor512KiB(b *testing.B) {
func BenchmarkGZIPCompressor1MiB(b *testing.B) {
bmCompressor(b, 1024*1024, NewGZIPCompressor())
}

// mustCompress compresses the input data and returns a BufferSlice.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this function is more like compressWithDeterministicError so that we can do comparison in our test cases?

}

if doesReceiveMessageOverflow(out.Len(), maxReceiveMessageSize, dcReader) {
return nil, errMaxMessageSizeExceeded
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to do out.Free() here because we have already read upto maxReceiveMessageSize ?

@arjan-bal arjan-bal changed the title grpc: fix receiving empty messages when compression is enabled and maxReceiveMessageSize is maxInt64 #7753 grpc: fix receiving empty messages when compression is enabled and maxReceiveMessageSize is maxInt #7753 Dec 24, 2024
@arjan-bal arjan-bal changed the title grpc: fix receiving empty messages when compression is enabled and maxReceiveMessageSize is maxInt #7753 grpc: fix receiving empty messages when compression is enabled and maxReceiveMessageSize is MaxInt #7753 Dec 24, 2024
Copy link
Contributor

@arjan-bal arjan-bal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Adding a second reviewer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Get empty response when compress enabled and maxReceiveMessageSize be maxInt64
8 participants