google.golang.org/grpc@v1.72.2/mem/buffers_test.go (about)

     1  /*
     2   *
     3   * Copyright 2024 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package mem_test
    20  
    21  import (
    22  	"bytes"
    23  	"testing"
    24  
    25  	"google.golang.org/grpc/internal"
    26  	"google.golang.org/grpc/internal/grpctest"
    27  	"google.golang.org/grpc/mem"
    28  )
    29  
    30  type s struct {
    31  	grpctest.Tester
    32  }
    33  
    34  func Test(t *testing.T) {
    35  	internal.SetBufferPoolingThresholdForTesting.(func(int))(0)
    36  
    37  	grpctest.RunSubTests(t, s{})
    38  }
    39  
    40  // Tests that a buffer created with NewBuffer, which when later freed, invokes
    41  // the free function with the correct data.
    42  func (s) TestBuffer_NewBufferAndFree(t *testing.T) {
    43  	data := "abcd"
    44  	freed := false
    45  	freeF := poolFunc(func(got *[]byte) {
    46  		if !bytes.Equal(*got, []byte(data)) {
    47  			t.Fatalf("Free function called with bytes %s, want %s", string(*got), data)
    48  		}
    49  		freed = true
    50  	})
    51  
    52  	buf := newBuffer([]byte(data), freeF)
    53  	if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
    54  		t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
    55  	}
    56  
    57  	// Verify that the free function is invoked when all references are freed.
    58  	buf.Free()
    59  	if !freed {
    60  		t.Fatalf("Buffer not freed")
    61  	}
    62  }
    63  
    64  // Tests that a buffer created with NewBuffer, on which an additional reference
    65  // is acquired, which when later freed, invokes the free function with the
    66  // correct data, but only after all references are released.
    67  func (s) TestBuffer_NewBufferRefAndFree(t *testing.T) {
    68  	data := "abcd"
    69  	freed := false
    70  	freeF := poolFunc(func(got *[]byte) {
    71  		if !bytes.Equal(*got, []byte(data)) {
    72  			t.Fatalf("Free function called with bytes %s, want %s", string(*got), string(data))
    73  		}
    74  		freed = true
    75  	})
    76  
    77  	buf := newBuffer([]byte(data), freeF)
    78  	if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
    79  		t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
    80  	}
    81  
    82  	buf.Ref()
    83  	if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
    84  		t.Fatalf("New reference to the Buffer contains data %s, want %s", string(got), string(data))
    85  	}
    86  
    87  	// Verify that the free function is not invoked when all references are yet
    88  	// to be freed.
    89  	buf.Free()
    90  	if freed {
    91  		t.Fatalf("Free function called before all references freed")
    92  	}
    93  
    94  	// Verify that the free function is invoked when all references are freed.
    95  	buf.Free()
    96  	if !freed {
    97  		t.Fatalf("Buffer not freed")
    98  	}
    99  }
   100  
   101  func (s) TestBuffer_NewBufferHandlesShortBuffers(t *testing.T) {
   102  	const threshold = 100
   103  
   104  	// Update the pooling threshold, since that's what's being tested.
   105  	internal.SetBufferPoolingThresholdForTesting.(func(int))(threshold)
   106  	t.Cleanup(func() {
   107  		internal.SetBufferPoolingThresholdForTesting.(func(int))(0)
   108  	})
   109  
   110  	// Make a pool with a buffer whose capacity is larger than the pooling
   111  	// threshold, but whose length is less than the threshold.
   112  	b := make([]byte, threshold/2, threshold*2)
   113  	pool := &singleBufferPool{
   114  		t:    t,
   115  		data: &b,
   116  	}
   117  
   118  	// Get a Buffer, then free it. If NewBuffer decided that the Buffer
   119  	// shouldn't get pooled, Free will be a noop and singleBufferPool will not
   120  	// have been updated.
   121  	mem.NewBuffer(&b, pool).Free()
   122  
   123  	if pool.data != nil {
   124  		t.Fatalf("Buffer not returned to pool")
   125  	}
   126  }
   127  
   128  func (s) TestBuffer_FreeAfterFree(t *testing.T) {
   129  	buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
   130  	if buf.Len() != 4 {
   131  		t.Fatalf("Buffer length is %d, want 4", buf.Len())
   132  	}
   133  
   134  	// Ensure that a double free does panic.
   135  	buf.Free()
   136  	defer checkForPanic(t, "Cannot free freed buffer")
   137  	buf.Free()
   138  }
   139  
   140  type singleBufferPool struct {
   141  	t    *testing.T
   142  	data *[]byte
   143  }
   144  
   145  func (s *singleBufferPool) Get(length int) *[]byte {
   146  	if len(*s.data) != length {
   147  		s.t.Fatalf("Invalid requested length, got %d want %d", length, len(*s.data))
   148  	}
   149  	return s.data
   150  }
   151  
   152  func (s *singleBufferPool) Put(b *[]byte) {
   153  	if s.data != b {
   154  		s.t.Fatalf("Wrong buffer returned to pool, got %p want %p", b, s.data)
   155  	}
   156  	s.data = nil
   157  }
   158  
   159  // Tests that a buffer created with Copy, which when later freed, returns the underlying
   160  // byte slice to the buffer pool.
   161  func (s) TestBuffer_CopyAndFree(t *testing.T) {
   162  	data := []byte("abcd")
   163  	testPool := &singleBufferPool{
   164  		t:    t,
   165  		data: &data,
   166  	}
   167  
   168  	buf := mem.Copy(data, testPool)
   169  	if got := buf.ReadOnlyData(); !bytes.Equal(got, data) {
   170  		t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
   171  	}
   172  
   173  	// Verify that the free function is invoked when all references are freed.
   174  	buf.Free()
   175  	if testPool.data != nil {
   176  		t.Fatalf("Buffer not freed")
   177  	}
   178  }
   179  
   180  // Tests that a buffer created with Copy, on which an additional reference is
   181  // acquired, which when later freed, returns the underlying byte slice to the
   182  // buffer pool.
   183  func (s) TestBuffer_CopyRefAndFree(t *testing.T) {
   184  	data := []byte("abcd")
   185  	testPool := &singleBufferPool{
   186  		t:    t,
   187  		data: &data,
   188  	}
   189  
   190  	buf := mem.Copy(data, testPool)
   191  	if got := buf.ReadOnlyData(); !bytes.Equal(got, data) {
   192  		t.Fatalf("Buffer contains data %s, want %s", string(got), string(data))
   193  	}
   194  
   195  	buf.Ref()
   196  	if got := buf.ReadOnlyData(); !bytes.Equal(got, []byte(data)) {
   197  		t.Fatalf("New reference to the Buffer contains data %s, want %s", string(got), string(data))
   198  	}
   199  
   200  	// Verify that the free function is not invoked when all references are yet
   201  	// to be freed.
   202  	buf.Free()
   203  	if testPool.data == nil {
   204  		t.Fatalf("Free function called before all references freed")
   205  	}
   206  
   207  	// Verify that the free function is invoked when all references are freed.
   208  	buf.Free()
   209  	if testPool.data != nil {
   210  		t.Fatalf("Free never called")
   211  	}
   212  }
   213  
   214  func (s) TestBuffer_ReadOnlyDataAfterFree(t *testing.T) {
   215  	// Verify that reading before freeing does not panic.
   216  	buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
   217  	buf.ReadOnlyData()
   218  
   219  	buf.Free()
   220  	defer checkForPanic(t, "Cannot read freed buffer")
   221  	buf.ReadOnlyData()
   222  }
   223  
   224  func (s) TestBuffer_RefAfterFree(t *testing.T) {
   225  	// Verify that acquiring a ref before freeing does not panic.
   226  	buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
   227  	buf.Ref()
   228  
   229  	// This first call should not panic and bring the ref counter down to 1
   230  	buf.Free()
   231  	// This second call actually frees the buffer
   232  	buf.Free()
   233  	defer checkForPanic(t, "Cannot ref freed buffer")
   234  	buf.Ref()
   235  }
   236  
   237  func (s) TestBuffer_SplitAfterFree(t *testing.T) {
   238  	// Verify that splitting before freeing does not panic.
   239  	buf := newBuffer([]byte("abcd"), mem.NopBufferPool{})
   240  	buf, bufSplit := mem.SplitUnsafe(buf, 2)
   241  
   242  	bufSplit.Free()
   243  	buf.Free()
   244  	defer checkForPanic(t, "Cannot split freed buffer")
   245  	mem.SplitUnsafe(buf, 2)
   246  }
   247  
   248  type poolFunc func(*[]byte)
   249  
   250  func (p poolFunc) Get(length int) *[]byte {
   251  	panic("Get should never be called")
   252  }
   253  
   254  func (p poolFunc) Put(i *[]byte) {
   255  	p(i)
   256  }
   257  
   258  func (s) TestBuffer_Split(t *testing.T) {
   259  	ready := false
   260  	freed := false
   261  	data := []byte{1, 2, 3, 4}
   262  	buf := mem.NewBuffer(&data, poolFunc(func(bytes *[]byte) {
   263  		if !ready {
   264  			t.Fatalf("Freed too early")
   265  		}
   266  		freed = true
   267  	}))
   268  	checkBufData := func(b mem.Buffer, expected []byte) {
   269  		t.Helper()
   270  		if !bytes.Equal(b.ReadOnlyData(), expected) {
   271  			t.Fatalf("Buffer did not contain expected data %v, got %v", expected, b.ReadOnlyData())
   272  		}
   273  	}
   274  
   275  	buf, split1 := mem.SplitUnsafe(buf, 2)
   276  	checkBufData(buf, data[:2])
   277  	checkBufData(split1, data[2:])
   278  
   279  	// Check that splitting the buffer more than once works as intended.
   280  	split1, split2 := mem.SplitUnsafe(split1, 1)
   281  	checkBufData(split1, data[2:3])
   282  	checkBufData(split2, data[3:])
   283  
   284  	// If any of the following frees actually free the buffer, the test will fail.
   285  	buf.Free()
   286  	split2.Free()
   287  
   288  	ready = true
   289  	split1.Free()
   290  
   291  	if !freed {
   292  		t.Fatalf("Buffer never freed")
   293  	}
   294  }
   295  
   296  func checkForPanic(t *testing.T, wantErr string) {
   297  	t.Helper()
   298  	r := recover()
   299  	if r == nil {
   300  		t.Fatalf("Use after free did not panic")
   301  	}
   302  	if msg, ok := r.(string); !ok || msg != wantErr {
   303  		t.Fatalf("panic called with %v, want %s", r, wantErr)
   304  	}
   305  }