mirror of https://github.com/grpc/grpc-go.git
306 lines
8.0 KiB
Go
306 lines
8.0 KiB
Go
/*
|
|
*
|
|
* Copyright 2024 gRPC authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
package mem_test
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"google.golang.org/grpc/internal"
|
|
"google.golang.org/grpc/internal/grpctest"
|
|
"google.golang.org/grpc/mem"
|
|
)
|
|
|
|
type s struct {
|
|
grpctest.Tester
|
|
}
|
|
|
|
func Test(t *testing.T) {
|
|
internal.SetBufferPoolingThresholdForTesting.(func(int))(0)
|
|
|
|
grpctest.RunSubTests(t, s{})
|
|
}
|
|
|
|
// Tests that a buffer created with NewBuffer, which when later freed, invokes
|
|
// the free function with the correct data.
|
|
func (s) TestBuffer_NewBufferAndFree(t *testing.T) {
|
|
data := "abcd"
|
|
freed := false
|
|
freeF := poolFunc(func(got *[]byte) {
|
|
if !bytes.Equal(*got, []byte(data)) {
|
|
t.Fatalf("Free function called with bytes %s, want %s", string(*got), data)
|
|
}
|
|
freed = true
|
|
})
|
|
|
|
buf := newBuffer([]byte(data), freeF)
|
|
if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
|
|
t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
|
|
}
|
|
|
|
// Verify that the free function is invoked when all references are freed.
|
|
buf.Free()
|
|
if !freed {
|
|
t.Fatalf("Buffer not freed")
|
|
}
|
|
}
|
|
|
|
// Tests that a buffer created with NewBuffer, on which an additional reference
|
|
// is acquired, which when later freed, invokes the free function with the
|
|
// correct data, but only after all references are released.
|
|
func (s) TestBuffer_NewBufferRefAndFree(t *testing.T) {
|
|
data := "abcd"
|
|
freed := false
|
|
freeF := poolFunc(func(got *[]byte) {
|
|
if !bytes.Equal(*got, []byte(data)) {
|
|
t.Fatalf("Free function called with bytes %s, want %s", string(*got), string(data))
|
|
}
|
|
freed = true
|
|
})
|
|
|
|
buf := newBuffer([]byte(data), freeF)
|
|
if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
|
|
t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
|
|
}
|
|
|
|
buf.Ref()
|
|
if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
|
|
t.Fatalf("New reference to the Buffer contains data %s, want %s", string(got), string(data))
|
|
}
|
|
|
|
// Verify that the free function is not invoked when all references are yet
|
|
// to be freed.
|
|
buf.Free()
|
|
if freed {
|
|
t.Fatalf("Free function called before all references freed")
|
|
}
|
|
|
|
// Verify that the free function is invoked when all references are freed.
|
|
buf.Free()
|
|
if !freed {
|
|
t.Fatalf("Buffer not freed")
|
|
}
|
|
}
|
|
|
|
func (s) TestBuffer_NewBufferHandlesShortBuffers(t *testing.T) {
|
|
const threshold = 100
|
|
|
|
// Update the pooling threshold, since that's what's being tested.
|
|
internal.SetBufferPoolingThresholdForTesting.(func(int))(threshold)
|
|
t.Cleanup(func() {
|
|
internal.SetBufferPoolingThresholdForTesting.(func(int))(0)
|
|
})
|
|
|
|
// Make a pool with a buffer whose capacity is larger than the pooling
|
|
// threshold, but whose length is less than the threshold.
|
|
b := make([]byte, threshold/2, threshold*2)
|
|
pool := &singleBufferPool{
|
|
t: t,
|
|
data: &b,
|
|
}
|
|
|
|
// Get a Buffer, then free it. If NewBuffer decided that the Buffer
|
|
// shouldn't get pooled, Free will be a noop and singleBufferPool will not
|
|
// have been updated.
|
|
mem.NewBuffer(&b, pool).Free()
|
|
|
|
if pool.data != nil {
|
|
t.Fatalf("Buffer not returned to pool")
|
|
}
|
|
}
|
|
|
|
func (s) TestBuffer_FreeAfterFree(t *testing.T) {
|
|
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
|
|
if buf.Len() != 4 {
|
|
t.Fatalf("Buffer length is %d, want 4", buf.Len())
|
|
}
|
|
|
|
// Ensure that a double free does panic.
|
|
buf.Free()
|
|
defer checkForPanic(t, "Cannot free freed buffer")
|
|
buf.Free()
|
|
}
|
|
|
|
type singleBufferPool struct {
|
|
t *testing.T
|
|
data *[]byte
|
|
}
|
|
|
|
func (s *singleBufferPool) Get(length int) *[]byte {
|
|
if len(*s.data) != length {
|
|
s.t.Fatalf("Invalid requested length, got %d want %d", length, len(*s.data))
|
|
}
|
|
return s.data
|
|
}
|
|
|
|
func (s *singleBufferPool) Put(b *[]byte) {
|
|
if s.data != b {
|
|
s.t.Fatalf("Wrong buffer returned to pool, got %p want %p", b, s.data)
|
|
}
|
|
s.data = nil
|
|
}
|
|
|
|
// Tests that a buffer created with Copy, which when later freed, returns the underlying
|
|
// byte slice to the buffer pool.
|
|
func (s) TestBuffer_CopyAndFree(t *testing.T) {
|
|
data := []byte("abcd")
|
|
testPool := &singleBufferPool{
|
|
t: t,
|
|
data: &data,
|
|
}
|
|
|
|
buf := mem.Copy(data, testPool)
|
|
if got := buf.ReadOnlyData(); !bytes.Equal(got, data) {
|
|
t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
|
|
}
|
|
|
|
// Verify that the free function is invoked when all references are freed.
|
|
buf.Free()
|
|
if testPool.data != nil {
|
|
t.Fatalf("Buffer not freed")
|
|
}
|
|
}
|
|
|
|
// Tests that a buffer created with Copy, on which an additional reference is
|
|
// acquired, which when later freed, returns the underlying byte slice to the
|
|
// buffer pool.
|
|
func (s) TestBuffer_CopyRefAndFree(t *testing.T) {
|
|
data := []byte("abcd")
|
|
testPool := &singleBufferPool{
|
|
t: t,
|
|
data: &data,
|
|
}
|
|
|
|
buf := mem.Copy(data, testPool)
|
|
if got := buf.ReadOnlyData(); !bytes.Equal(got, data) {
|
|
t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
|
|
}
|
|
|
|
buf.Ref()
|
|
if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
|
|
t.Fatalf("New reference to the Buffer contains data %s, want %s", string(got), string(data))
|
|
}
|
|
|
|
// Verify that the free function is not invoked when all references are yet
|
|
// to be freed.
|
|
buf.Free()
|
|
if testPool.data == nil {
|
|
t.Fatalf("Free function called before all references freed")
|
|
}
|
|
|
|
// Verify that the free function is invoked when all references are freed.
|
|
buf.Free()
|
|
if testPool.data != nil {
|
|
t.Fatalf("Free never called")
|
|
}
|
|
}
|
|
|
|
func (s) TestBuffer_ReadOnlyDataAfterFree(t *testing.T) {
|
|
// Verify that reading before freeing does not panic.
|
|
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
|
|
buf.ReadOnlyData()
|
|
|
|
buf.Free()
|
|
defer checkForPanic(t, "Cannot read freed buffer")
|
|
buf.ReadOnlyData()
|
|
}
|
|
|
|
func (s) TestBuffer_RefAfterFree(t *testing.T) {
|
|
// Verify that acquiring a ref before freeing does not panic.
|
|
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
|
|
buf.Ref()
|
|
|
|
// This first call should not panic and bring the ref counter down to 1
|
|
buf.Free()
|
|
// This second call actually frees the buffer
|
|
buf.Free()
|
|
defer checkForPanic(t, "Cannot ref freed buffer")
|
|
buf.Ref()
|
|
}
|
|
|
|
func (s) TestBuffer_SplitAfterFree(t *testing.T) {
|
|
// Verify that splitting before freeing does not panic.
|
|
buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
|
|
buf, bufSplit := mem.SplitUnsafe(buf, 2)
|
|
|
|
bufSplit.Free()
|
|
buf.Free()
|
|
defer checkForPanic(t, "Cannot split freed buffer")
|
|
mem.SplitUnsafe(buf, 2)
|
|
}
|
|
|
|
type poolFunc func(*[]byte)
|
|
|
|
func (p poolFunc) Get(length int) *[]byte {
|
|
panic("Get should never be called")
|
|
}
|
|
|
|
func (p poolFunc) Put(i *[]byte) {
|
|
p(i)
|
|
}
|
|
|
|
func (s) TestBuffer_Split(t *testing.T) {
|
|
ready := false
|
|
freed := false
|
|
data := []byte{1, 2, 3, 4}
|
|
buf := mem.NewBuffer(&data, poolFunc(func(bytes *[]byte) {
|
|
if !ready {
|
|
t.Fatalf("Freed too early")
|
|
}
|
|
freed = true
|
|
}))
|
|
checkBufData := func(b mem.Buffer, expected []byte) {
|
|
t.Helper()
|
|
if !bytes.Equal(b.ReadOnlyData(), expected) {
|
|
t.Fatalf("Buffer did not contain expected data %v, got %v", expected, b.ReadOnlyData())
|
|
}
|
|
}
|
|
|
|
buf, split1 := mem.SplitUnsafe(buf, 2)
|
|
checkBufData(buf, data[:2])
|
|
checkBufData(split1, data[2:])
|
|
|
|
// Check that splitting the buffer more than once works as intended.
|
|
split1, split2 := mem.SplitUnsafe(split1, 1)
|
|
checkBufData(split1, data[2:3])
|
|
checkBufData(split2, data[3:])
|
|
|
|
// If any of the following frees actually free the buffer, the test will fail.
|
|
buf.Free()
|
|
split2.Free()
|
|
|
|
ready = true
|
|
split1.Free()
|
|
|
|
if !freed {
|
|
t.Fatalf("Buffer never freed")
|
|
}
|
|
}
|
|
|
|
func checkForPanic(t *testing.T, wantErr string) {
|
|
t.Helper()
|
|
r := recover()
|
|
if r == nil {
|
|
t.Fatalf("Use after free did not panic")
|
|
}
|
|
if msg, ok := r.(string); !ok || msg != wantErr {
|
|
t.Fatalf("panic called with %v, want %s", r, wantErr)
|
|
}
|
|
}
|