github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/quotapool/intpool_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package quotapool_test
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"math"
    17  	"math/rand"
    18  	"runtime"
    19  	"strconv"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/cockroachdb/cockroach/pkg/testutils"
    25  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    26  	"github.com/cockroachdb/cockroach/pkg/util/quotapool"
    27  	"github.com/cockroachdb/errors"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  // logSlowAcquisition is an Option to log slow acquisitions.
    33  var logSlowAcquisition = quotapool.OnSlowAcquisition(2*time.Second, quotapool.LogSlowAcquisition)
    34  
    35  // TestQuotaPoolBasic tests the minimal expected behavior of the quota pool
    36  // with different sized quota pool and a varying number of goroutines, each
    37  // acquiring a unit quota and releasing it immediately after.
    38  func TestQuotaPoolBasic(t *testing.T) {
    39  	defer leaktest.AfterTest(t)()
    40  
    41  	quotas := []uint64{1, 10, 100, 1000}
    42  	goroutineCounts := []int{1, 10, 100}
    43  
    44  	for _, quota := range quotas {
    45  		for _, numGoroutines := range goroutineCounts {
    46  			qp := quotapool.NewIntPool("test", quota)
    47  			ctx := context.Background()
    48  			resCh := make(chan error, numGoroutines)
    49  
    50  			for i := 0; i < numGoroutines; i++ {
    51  				go func() {
    52  					alloc, err := qp.Acquire(ctx, 1)
    53  					if err != nil {
    54  						resCh <- err
    55  						return
    56  					}
    57  					alloc.Release()
    58  					resCh <- nil
    59  				}()
    60  			}
    61  
    62  			for i := 0; i < numGoroutines; i++ {
    63  				select {
    64  				case <-time.After(5 * time.Second):
    65  					t.Fatal("did not complete acquisitions within 5s")
    66  				case err := <-resCh:
    67  					if err != nil {
    68  						t.Fatal(err)
    69  					}
    70  				}
    71  			}
    72  
    73  			if q := qp.ApproximateQuota(); q != quota {
    74  				t.Fatalf("expected quota: %d, got: %d", quota, q)
    75  			}
    76  		}
    77  	}
    78  }
    79  
    80  // TestQuotaPoolContextCancellation tests the behavior that for an ongoing
    81  // blocked acquisition, if the context passed in gets canceled the acquisition
    82  // gets canceled too with an error indicating so. This should not affect the
    83  // available quota in the pool.
    84  func TestQuotaPoolContextCancellation(t *testing.T) {
    85  	defer leaktest.AfterTest(t)()
    86  
    87  	ctx, cancel := context.WithCancel(context.Background())
    88  	qp := quotapool.NewIntPool("test", 1)
    89  	alloc, err := qp.Acquire(ctx, 1)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  
    94  	errCh := make(chan error, 1)
    95  	go func() {
    96  		_, canceledErr := qp.Acquire(ctx, 1)
    97  		errCh <- canceledErr
    98  	}()
    99  
   100  	cancel()
   101  
   102  	select {
   103  	case <-time.After(5 * time.Second):
   104  		t.Fatal("context cancellation did not unblock acquisitions within 5s")
   105  	case err := <-errCh:
   106  		if !errors.Is(err, context.Canceled) {
   107  			t.Fatalf("expected context cancellation error, got %v", err)
   108  		}
   109  	}
   110  
   111  	alloc.Release()
   112  
   113  	if q := qp.ApproximateQuota(); q != 1 {
   114  		t.Fatalf("expected quota: 1, got: %d", q)
   115  	}
   116  }
   117  
   118  // TestQuotaPoolClose tests the behavior that for an ongoing blocked
   119  // acquisition if the quota pool gets closed, all ongoing and subsequent
   120  // acquisitions return an *ErrClosed.
   121  func TestQuotaPoolClose(t *testing.T) {
   122  	defer leaktest.AfterTest(t)()
   123  
   124  	ctx := context.Background()
   125  	qp := quotapool.NewIntPool("test", 1)
   126  	if _, err := qp.Acquire(ctx, 1); err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	const numGoroutines = 5
   130  	resCh := make(chan error, numGoroutines)
   131  
   132  	tryAcquire := func() {
   133  		_, err := qp.Acquire(ctx, 1)
   134  		resCh <- err
   135  	}
   136  	for i := 0; i < numGoroutines; i++ {
   137  		go tryAcquire()
   138  	}
   139  
   140  	qp.Close("")
   141  
   142  	// Second call should be a no-op.
   143  	qp.Close("")
   144  
   145  	for i := 0; i < numGoroutines; i++ {
   146  		select {
   147  		case <-time.After(5 * time.Second):
   148  			t.Fatal("quota pool closing did not unblock acquisitions within 5s")
   149  		case err := <-resCh:
   150  			if !errors.HasType(err, (*quotapool.ErrClosed)(nil)) {
   151  				t.Fatal(err)
   152  			}
   153  		}
   154  	}
   155  
   156  	go tryAcquire()
   157  
   158  	select {
   159  	case <-time.After(5 * time.Second):
   160  		t.Fatal("quota pool closing did not unblock acquisitions within 5s")
   161  	case err := <-resCh:
   162  		if !errors.HasType(err, (*quotapool.ErrClosed)(nil)) {
   163  			t.Fatal(err)
   164  		}
   165  	}
   166  }
   167  
   168  // TestQuotaPoolCanceledAcquisitions tests the behavior where we enqueue
   169  // multiple acquisitions with canceled contexts and expect any subsequent
   170  // acquisition with a valid context to proceed without error.
   171  func TestQuotaPoolCanceledAcquisitions(t *testing.T) {
   172  	defer leaktest.AfterTest(t)()
   173  
   174  	ctx, cancel := context.WithCancel(context.Background())
   175  	qp := quotapool.NewIntPool("test", 1)
   176  	alloc, err := qp.Acquire(ctx, 1)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  
   181  	cancel()
   182  	const numGoroutines = 5
   183  
   184  	errCh := make(chan error)
   185  	for i := 0; i < numGoroutines; i++ {
   186  		go func() {
   187  			_, err := qp.Acquire(ctx, 1)
   188  			errCh <- err
   189  		}()
   190  	}
   191  
   192  	for i := 0; i < numGoroutines; i++ {
   193  		select {
   194  		case <-time.After(5 * time.Second):
   195  			t.Fatal("context cancellations did not unblock acquisitions within 5s")
   196  		case err := <-errCh:
   197  			if !errors.Is(err, context.Canceled) {
   198  				t.Fatalf("expected context cancellation error, got %v", err)
   199  			}
   200  		}
   201  	}
   202  
   203  	alloc.Release()
   204  	go func() {
   205  		_, err := qp.Acquire(context.Background(), 1)
   206  		errCh <- err
   207  	}()
   208  
   209  	select {
   210  	case err := <-errCh:
   211  		if err != nil {
   212  			t.Fatal(err)
   213  		}
   214  	case <-time.After(5 * time.Second):
   215  		t.Fatal("acquisition didn't go through within 5s")
   216  	}
   217  }
   218  
   219  // TestQuotaPoolNoops tests that quota pool operations that should be noops are
   220  // so, e.g. quotaPool.acquire(0) and quotaPool.release(0).
   221  func TestQuotaPoolNoops(t *testing.T) {
   222  	defer leaktest.AfterTest(t)()
   223  
   224  	qp := quotapool.NewIntPool("test", 1)
   225  	ctx := context.Background()
   226  	initialAlloc, err := qp.Acquire(ctx, 1)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  
   231  	// Acquisition of blockedAlloc will block until initialAlloc is released.
   232  	errCh := make(chan error)
   233  	var blockedAlloc *quotapool.IntAlloc
   234  	go func() {
   235  		blockedAlloc, err = qp.Acquire(ctx, 1)
   236  		errCh <- err
   237  	}()
   238  
   239  	// Allocation of zero should not block.
   240  	emptyAlloc, err := qp.Acquire(ctx, 0)
   241  	if err != nil {
   242  		t.Fatalf("failed to acquire 0 quota: %v", err)
   243  	}
   244  	emptyAlloc.Release() // Release of 0 should do nothing
   245  
   246  	initialAlloc.Release()
   247  	select {
   248  	case <-time.After(5 * time.Second):
   249  		t.Fatal("context cancellations did not unblock acquisitions within 5s")
   250  	case err := <-errCh:
   251  		if err != nil {
   252  			t.Fatal(err)
   253  		}
   254  	}
   255  	if q := qp.ApproximateQuota(); q != 0 {
   256  		t.Fatalf("expected quota: 0, got: %d", q)
   257  	}
   258  	blockedAlloc.Release()
   259  	if q := qp.ApproximateQuota(); q != 1 {
   260  		t.Fatalf("expected quota: 1, got: %d", q)
   261  	}
   262  }
   263  
   264  // TestQuotaPoolMaxQuota tests that Acquire cannot acquire more than the
   265  // maximum amount with which the pool was initialized.
   266  func TestQuotaPoolMaxQuota(t *testing.T) {
   267  	defer leaktest.AfterTest(t)()
   268  
   269  	const quota = 100
   270  	qp := quotapool.NewIntPool("test", quota)
   271  	ctx := context.Background()
   272  	alloc, err := qp.Acquire(ctx, 2*quota)
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	if got := alloc.Acquired(); got != quota {
   277  		t.Fatalf("expected to acquire the capacity quota %d, instead got %d", quota, got)
   278  	}
   279  	alloc.Release()
   280  	if q := qp.ApproximateQuota(); q != quota {
   281  		t.Fatalf("expected quota: %d, got: %d", quota, q)
   282  	}
   283  }
   284  
   285  // TestQuotaPoolCappedAcquisition verifies that when an acquisition request
   286  // greater than the maximum quota is placed, we still allow the acquisition to
   287  // proceed but after having acquired the maximum quota amount.
   288  func TestQuotaPoolCappedAcquisition(t *testing.T) {
   289  	defer leaktest.AfterTest(t)()
   290  
   291  	const quota = 1
   292  	qp := quotapool.NewIntPool("test", quota)
   293  	alloc, err := qp.Acquire(context.Background(), quota*100)
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  
   298  	if q := qp.ApproximateQuota(); q != 0 {
   299  		t.Fatalf("expected quota: %d, got: %d", 0, q)
   300  	}
   301  
   302  	alloc.Release()
   303  	if q := qp.ApproximateQuota(); q != quota {
   304  		t.Fatalf("expected quota: %d, got: %d", quota, q)
   305  	}
   306  }
   307  
   308  // TestQuotaPoolZeroCapacity verifies that a non-noop acquisition request on a
   309  // pool with zero capacity is immediately rejected, regardless of whether the
   310  // request is permitted to wait or not.
   311  func TestQuotaPoolZeroCapacity(t *testing.T) {
   312  	defer leaktest.AfterTest(t)()
   313  
   314  	const quota = 0
   315  	qp := quotapool.NewIntPool("test", quota)
   316  	ctx := context.Background()
   317  
   318  	failed, err := qp.Acquire(ctx, 1)
   319  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   320  	require.Nil(t, failed)
   321  
   322  	failed, err = qp.TryAcquire(ctx, 1)
   323  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   324  	require.Nil(t, failed)
   325  
   326  	acq1, err := qp.Acquire(ctx, 0)
   327  	require.NoError(t, err)
   328  	acq1.Release()
   329  
   330  	acq2, err := qp.TryAcquire(ctx, 0)
   331  	require.NoError(t, err)
   332  	acq2.Release()
   333  }
   334  
   335  func TestOnAcquisition(t *testing.T) {
   336  	defer leaktest.AfterTest(t)()
   337  
   338  	const quota = 100
   339  	var called bool
   340  	qp := quotapool.NewIntPool("test", quota,
   341  		quotapool.OnAcquisition(func(ctx context.Context, poolName string, _ quotapool.Request, start time.Time,
   342  		) {
   343  			assert.Equal(t, poolName, "test")
   344  			called = true
   345  		}))
   346  	ctx := context.Background()
   347  	alloc, err := qp.Acquire(ctx, 1)
   348  	assert.Nil(t, err)
   349  	assert.True(t, called)
   350  	alloc.Release()
   351  }
   352  
   353  // TestSlowAcquisition ensures that the SlowAcquisition callback is called
   354  // when an Acquire call takes longer than the configured timeout.
   355  func TestSlowAcquisition(t *testing.T) {
   356  	defer leaktest.AfterTest(t)()
   357  
   358  	// The test will set up an IntPool with 1 quota and a SlowAcquisition callback
   359  	// which closes channels when called by the second goroutine. An initial call
   360  	// to Acquire will take all of the quota. Then a second call with go should be
   361  	// blocked leading to the callback being triggered.
   362  
   363  	// In order to prevent the first call to Acquire from triggering the callback
   364  	// we mark its context with a value.
   365  	ctx := context.Background()
   366  	type ctxKey int
   367  	firstKey := ctxKey(1)
   368  	firstCtx := context.WithValue(ctx, firstKey, "foo")
   369  	slowCalled, acquiredCalled := make(chan struct{}), make(chan struct{})
   370  	const poolName = "test"
   371  	f := func(ctx context.Context, name string, _ quotapool.Request, _ time.Time) func() {
   372  		assert.Equal(t, poolName, name)
   373  		if ctx.Value(firstKey) != nil {
   374  			return func() {}
   375  		}
   376  		close(slowCalled)
   377  		return func() {
   378  			close(acquiredCalled)
   379  		}
   380  	}
   381  	qp := quotapool.NewIntPool(poolName, 1, quotapool.OnSlowAcquisition(time.Microsecond, f))
   382  	alloc, err := qp.Acquire(firstCtx, 1)
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	go func() {
   387  		_, _ = qp.Acquire(ctx, 1)
   388  	}()
   389  	select {
   390  	case <-slowCalled:
   391  	case <-time.After(time.Second):
   392  		t.Fatalf("OnSlowAcquisition not called long after timeout")
   393  	}
   394  	select {
   395  	case <-acquiredCalled:
   396  		t.Fatalf("acquired callback called when insufficient quota was available")
   397  	default:
   398  	}
   399  	alloc.Release()
   400  	select {
   401  	case <-slowCalled:
   402  	case <-time.After(time.Second):
   403  		t.Fatalf("OnSlowAcquisition acquired callback not called long after timeout")
   404  	}
   405  }
   406  
   407  // Test that AcquireFunc() is called after IntAlloc.Freeze() is called - so that an ongoing acquisition gets
   408  // the chance to observe that there's no capacity for its request.
   409  func TestQuotaPoolCapacityDecrease(t *testing.T) {
   410  	defer leaktest.AfterTest(t)()
   411  
   412  	qp := quotapool.NewIntPool("test", 100)
   413  	ctx := context.Background()
   414  
   415  	alloc50, err := qp.Acquire(ctx, 50)
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  
   420  	first := true
   421  	firstCh := make(chan struct{})
   422  	doneCh := make(chan struct{})
   423  	go func() {
   424  		_, err = qp.AcquireFunc(ctx, func(_ context.Context, pi quotapool.PoolInfo) (took uint64, err error) {
   425  			if first {
   426  				first = false
   427  				close(firstCh)
   428  			}
   429  			if pi.Capacity < 100 {
   430  				return 0, fmt.Errorf("hopeless")
   431  			}
   432  			return 0, quotapool.ErrNotEnoughQuota
   433  		})
   434  		close(doneCh)
   435  	}()
   436  
   437  	// Wait for the callback to be called the first time. It should return ErrNotEnoughQuota.
   438  	<-firstCh
   439  	// Now leak the quota. This should call the callback to be called again.
   440  	alloc50.Freeze()
   441  	<-doneCh
   442  	if !testutils.IsError(err, "hopeless") {
   443  		t.Fatalf("expected hopeless error, got: %v", err)
   444  	}
   445  }
   446  
   447  func TestIntpoolNoWait(t *testing.T) {
   448  	defer leaktest.AfterTest(t)()
   449  
   450  	ctx := context.Background()
   451  	qp := quotapool.NewIntPool("test", 2)
   452  
   453  	acq1, err := qp.TryAcquire(ctx, 1)
   454  	require.NoError(t, err)
   455  
   456  	acq2, err := qp.TryAcquire(ctx, 1)
   457  	require.NoError(t, err)
   458  
   459  	failed, err := qp.TryAcquire(ctx, 1)
   460  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   461  	require.Nil(t, failed)
   462  
   463  	acq1.Release()
   464  
   465  	failed, err = qp.TryAcquire(ctx, 2)
   466  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   467  	require.Nil(t, failed)
   468  
   469  	acq2.Release()
   470  
   471  	acq5, err := qp.TryAcquire(ctx, 3)
   472  	require.NoError(t, err)
   473  	require.NotNil(t, acq5)
   474  
   475  	failed, err = qp.TryAcquireFunc(ctx, func(ctx context.Context, p quotapool.PoolInfo) (took uint64, err error) {
   476  		require.Equal(t, uint64(0), p.Available)
   477  		return 0, quotapool.ErrNotEnoughQuota
   478  	})
   479  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   480  	require.Nil(t, failed)
   481  
   482  	acq5.Release()
   483  
   484  	acq6, err := qp.TryAcquireFunc(ctx, func(ctx context.Context, p quotapool.PoolInfo) (took uint64, err error) {
   485  		return 1, nil
   486  	})
   487  	require.NoError(t, err)
   488  
   489  	acq6.Release()
   490  }
   491  
   492  // TestIntpoolRelease tests the Release method of intpool to ensure that it releases
   493  // what is expected and behaves as documented.
   494  func TestIntpoolRelease(t *testing.T) {
   495  	defer leaktest.AfterTest(t)()
   496  
   497  	ctx := context.Background()
   498  	const numPools = 3
   499  	const capacity = 3
   500  	// Populated full because it's handy for cases where all quota is returned.
   501  	var full [numPools]uint64
   502  	for i := 0; i < numPools; i++ {
   503  		full[i] = capacity
   504  	}
   505  	makePools := func() (pools [numPools]*quotapool.IntPool) {
   506  		for i := 0; i < numPools; i++ {
   507  			pools[i] = quotapool.NewIntPool(strconv.Itoa(i), capacity)
   508  		}
   509  		return pools
   510  	}
   511  
   512  	type acquisition struct {
   513  		pool int
   514  		q    uint64
   515  	}
   516  	type testCase struct {
   517  		toAcquire   []*acquisition
   518  		exclude     int
   519  		releasePool int
   520  		expQuota    [numPools]uint64
   521  	}
   522  	// First acquire all the quota, then release all but the trailing exclude
   523  	// allocs into the releasePool and ensure that the pools have expQuota.
   524  	// Finally release the rest of the allocs and ensure that the pools are full.
   525  	runTest := func(c *testCase) func(t *testing.T) {
   526  		return func(t *testing.T) {
   527  			pools := makePools()
   528  			allocs := make([]*quotapool.IntAlloc, len(c.toAcquire))
   529  			for i, acq := range c.toAcquire {
   530  				if acq == nil {
   531  					continue
   532  				}
   533  				require.LessOrEqual(t, acq.q, uint64(capacity))
   534  				alloc, err := pools[acq.pool].Acquire(ctx, acq.q)
   535  				require.NoError(t, err)
   536  				allocs[i] = alloc
   537  			}
   538  			prefix := len(allocs) - c.exclude
   539  			pools[c.releasePool].Release(allocs[:prefix]...)
   540  			for i, p := range pools {
   541  				require.Equal(t, c.expQuota[i], p.ApproximateQuota())
   542  			}
   543  			pools[c.releasePool].Release(allocs[prefix:]...)
   544  			for i, p := range pools {
   545  				require.Equal(t, full[i], p.ApproximateQuota())
   546  			}
   547  		}
   548  	}
   549  	for i, c := range []testCase{
   550  		{
   551  			toAcquire: []*acquisition{
   552  				{0, 1},
   553  				{1, 2},
   554  				{1, 1},
   555  				nil,
   556  			},
   557  			expQuota: full,
   558  		},
   559  		{
   560  			releasePool: 1,
   561  			toAcquire: []*acquisition{
   562  				{0, 1},
   563  				{1, 2},
   564  				{1, 1},
   565  				nil,
   566  			},
   567  			expQuota: full,
   568  		},
   569  		{
   570  			toAcquire: []*acquisition{
   571  				nil,
   572  				{0, capacity},
   573  				{1, capacity},
   574  				{2, capacity},
   575  			},
   576  			exclude:  1,
   577  			expQuota: [numPools]uint64{0: capacity, 1: capacity},
   578  		},
   579  		{
   580  			toAcquire: []*acquisition{
   581  				nil,
   582  				{0, capacity},
   583  				{1, capacity},
   584  				{2, capacity},
   585  			},
   586  			exclude:  3,
   587  			expQuota: [numPools]uint64{},
   588  		},
   589  	} {
   590  		t.Run(strconv.Itoa(i), runTest(&c))
   591  	}
   592  }
   593  
   594  // TestLen verifies that the Len() method of the IntPool works as expected.
   595  func TestLen(t *testing.T) {
   596  	defer leaktest.AfterTest(t)()
   597  
   598  	qp := quotapool.NewIntPool("test", 1, logSlowAcquisition)
   599  	ctx := context.Background()
   600  	allocCh := make(chan *quotapool.IntAlloc)
   601  	doAcquire := func(ctx context.Context) {
   602  		alloc, err := qp.Acquire(ctx, 1)
   603  		if ctx.Err() == nil && assert.Nil(t, err) {
   604  			allocCh <- alloc
   605  		}
   606  	}
   607  	assertLenSoon := func(exp int) {
   608  		testutils.SucceedsSoon(t, func() error {
   609  			if got := qp.Len(); got != exp {
   610  				return errors.Errorf("expected queue len to be %d, got %d", got, exp)
   611  			}
   612  			return nil
   613  		})
   614  	}
   615  	// Initially qp should have a length of 0.
   616  	assert.Equal(t, 0, qp.Len())
   617  	// Acquire all of the quota from the pool.
   618  	alloc, err := qp.Acquire(ctx, 1)
   619  	assert.Nil(t, err)
   620  	// The length should still be 0.
   621  	assert.Equal(t, 0, qp.Len())
   622  	// Launch a goroutine to acquire quota, ensure that the length increases.
   623  	go doAcquire(ctx)
   624  	assertLenSoon(1)
   625  	// Create more goroutines which will block to be canceled later in order to
   626  	// ensure that cancelations deduct from the length.
   627  	const numToCancel = 12 // an arbitrary number
   628  	ctxToCancel, cancel := context.WithCancel(ctx)
   629  	for i := 0; i < numToCancel; i++ {
   630  		go doAcquire(ctxToCancel)
   631  	}
   632  	// Ensure that all of the new goroutines are reflected in the length.
   633  	assertLenSoon(numToCancel + 1)
   634  	// Launch another goroutine with the default context.
   635  	go doAcquire(ctx)
   636  	assertLenSoon(numToCancel + 2)
   637  	// Cancel some of the goroutines.
   638  	cancel()
   639  	// Ensure that they are soon not reflected in the length.
   640  	assertLenSoon(2)
   641  	// Unblock the first goroutine.
   642  	alloc.Release()
   643  	alloc = <-allocCh
   644  	assert.Equal(t, 1, qp.Len())
   645  	// Unblock the second goroutine.
   646  	alloc.Release()
   647  	<-allocCh
   648  	assert.Equal(t, 0, qp.Len())
   649  }
   650  
   651  // TestIntpoolIllegalCapacity ensures that constructing an IntPool with capacity
   652  // in excess of math.MaxInt64 will panic.
   653  func TestIntpoolWithExcessCapacity(t *testing.T) {
   654  	defer leaktest.AfterTest(t)()
   655  	for _, c := range []uint64{
   656  		1, math.MaxInt64 - 1, math.MaxInt64,
   657  	} {
   658  		require.NotPanics(t, func() {
   659  			quotapool.NewIntPool("test", c)
   660  		})
   661  	}
   662  	for _, c := range []uint64{
   663  		math.MaxUint64, math.MaxUint64 - 1, math.MaxInt64 + 1,
   664  	} {
   665  		require.Panics(t, func() {
   666  			quotapool.NewIntPool("test", c)
   667  		})
   668  	}
   669  }
   670  
   671  // TestUpdateCapacityFluctuationsPermitExcessAllocs exercises the case where the
   672  // capacity of the IntPool fluctuates. We want to ensure that the amount of
   673  // outstanding quota never exceeds the largest capacity available during the
   674  // time when any of the outstanding allocations were acquired.
   675  func TestUpdateCapacityFluctuationsPreventsExcessAllocs(t *testing.T) {
   676  	defer leaktest.AfterTest(t)()
   677  
   678  	ctx := context.Background()
   679  	qp := quotapool.NewIntPool("test", 1)
   680  	var allocs []*quotapool.IntAlloc
   681  	allocCh := make(chan *quotapool.IntAlloc)
   682  	defer close(allocCh)
   683  	go func() {
   684  		for a := range allocCh {
   685  			if a == nil {
   686  				continue // allow nil channel sends to synchronize
   687  			}
   688  			allocs = append(allocs, a)
   689  		}
   690  	}()
   691  	acquireN := func(n int) {
   692  		var wg sync.WaitGroup
   693  		wg.Add(n)
   694  		for i := 0; i < n; i++ {
   695  			go func() {
   696  				defer wg.Done()
   697  				alloc, err := qp.Acquire(ctx, 1)
   698  				assert.NoError(t, err)
   699  				allocCh <- alloc
   700  			}()
   701  		}
   702  		wg.Wait()
   703  	}
   704  
   705  	acquireN(1)
   706  	qp.UpdateCapacity(100)
   707  	acquireN(99)
   708  	require.Equal(t, uint64(0), qp.ApproximateQuota())
   709  	qp.UpdateCapacity(1)
   710  	// Internally the representation of the quota should be -99 but we don't
   711  	// expose negative quota to users.
   712  	require.Equal(t, uint64(0), qp.ApproximateQuota())
   713  
   714  	// Update the capacity so now there's actually 0.
   715  	qp.UpdateCapacity(100)
   716  	require.Equal(t, uint64(0), qp.ApproximateQuota())
   717  	_, err := qp.TryAcquire(ctx, 1)
   718  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   719  
   720  	// Now update the capacity so that there's 1.
   721  	qp.UpdateCapacity(101)
   722  	require.Equal(t, uint64(1), qp.ApproximateQuota())
   723  	_, err = qp.TryAcquire(ctx, 2)
   724  	require.Equal(t, quotapool.ErrNotEnoughQuota, err)
   725  	acquireN(1)
   726  	allocCh <- nil // synchronize
   727  	// Release all of the quota back to the pool.
   728  	for _, a := range allocs {
   729  		a.Release()
   730  	}
   731  	allocs = nil
   732  	require.Equal(t, uint64(101), qp.ApproximateQuota())
   733  }
   734  
   735  func TestQuotaPoolUpdateCapacity(t *testing.T) {
   736  	defer leaktest.AfterTest(t)()
   737  
   738  	ctx := context.Background()
   739  	ch := make(chan *quotapool.IntAlloc, 1)
   740  	qp := quotapool.NewIntPool("test", 1)
   741  	alloc, err := qp.Acquire(ctx, 1)
   742  	require.NoError(t, err)
   743  	go func() {
   744  		blocked, err := qp.Acquire(ctx, 2)
   745  		assert.NoError(t, err)
   746  		ch <- blocked
   747  	}()
   748  	ensureBlocked := func() {
   749  		t.Helper()
   750  		select {
   751  		case <-time.After(10 * time.Millisecond): // ensure the acquisition fails for now
   752  		case got := <-ch:
   753  			got.Release()
   754  			t.Fatal("expected acquisition to fail")
   755  		}
   756  	}
   757  	ensureBlocked()
   758  	// Update the capacity to 2, the request should still be blocked.
   759  	qp.UpdateCapacity(2)
   760  	ensureBlocked()
   761  	qp.UpdateCapacity(3)
   762  	got := <-ch
   763  	require.Equal(t, uint64(2), got.Acquired())
   764  	require.Equal(t, uint64(3), qp.Capacity())
   765  	alloc.Release()
   766  	require.Equal(t, uint64(1), qp.ApproximateQuota())
   767  }
   768  
   769  func TestConcurrentUpdatesAndAcquisitions(t *testing.T) {
   770  	defer leaktest.AfterTest(t)()
   771  
   772  	ctx := context.Background()
   773  	var wg sync.WaitGroup
   774  	const maxCap = 100
   775  	qp := quotapool.NewIntPool("test", maxCap, logSlowAcquisition)
   776  	const N = 100
   777  	for i := 0; i < N; i++ {
   778  		wg.Add(1)
   779  		go func() {
   780  			defer wg.Done()
   781  			runtime.Gosched()
   782  			newCap := uint64(rand.Intn(maxCap-1)) + 1
   783  			qp.UpdateCapacity(newCap)
   784  		}()
   785  	}
   786  	for i := 0; i < N; i++ {
   787  		wg.Add(1)
   788  		go func() {
   789  			defer wg.Done()
   790  			runtime.Gosched()
   791  			acq, err := qp.Acquire(ctx, uint64(rand.Intn(maxCap)))
   792  			assert.NoError(t, err)
   793  			runtime.Gosched()
   794  			acq.Release()
   795  		}()
   796  	}
   797  	wg.Wait()
   798  	qp.UpdateCapacity(maxCap)
   799  	assert.Equal(t, uint64(maxCap), qp.ApproximateQuota())
   800  }
   801  
   802  // This test ensures that if you attempt to freeze an alloc which would make
   803  // the IntPool have negative capacity a panic occurs.
   804  func TestFreezeUnavailableCapacityPanics(t *testing.T) {
   805  	defer leaktest.AfterTest(t)()
   806  
   807  	ctx := context.Background()
   808  	qp := quotapool.NewIntPool("test", 10)
   809  	acq, err := qp.Acquire(ctx, 10)
   810  	require.NoError(t, err)
   811  	qp.UpdateCapacity(9)
   812  	require.Panics(t, func() {
   813  		acq.Freeze()
   814  	})
   815  }