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 }