github.com/jackc/puddle/v2@v2.2.2-0.20240301145809-72b022bcfc59/pool_test.go (about) 1 package puddle_test 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "log" 8 "math/rand" 9 "net" 10 "os" 11 "runtime" 12 "strconv" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/jackc/puddle/v2" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 "golang.org/x/sync/semaphore" 21 ) 22 23 type Counter struct { 24 mutex sync.Mutex 25 n int 26 } 27 28 // Next increments the counter and returns the value 29 func (c *Counter) Next() int { 30 c.mutex.Lock() 31 defer c.mutex.Unlock() 32 33 c.n += 1 34 return c.n 35 } 36 37 // Value returns the counter 38 func (c *Counter) Value() int { 39 c.mutex.Lock() 40 defer c.mutex.Unlock() 41 42 return c.n 43 } 44 45 func createConstructor() (puddle.Constructor[int], *Counter) { 46 var c Counter 47 f := func(ctx context.Context) (int, error) { 48 return c.Next(), nil 49 } 50 return f, &c 51 } 52 53 func stubDestructor(int) {} 54 55 func TestNewPoolRequiresMaxSizeGreaterThan0(t *testing.T) { 56 constructor, _ := createConstructor() 57 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: -1}) 58 assert.Nil(t, pool) 59 assert.Error(t, err) 60 61 pool, err = puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 0}) 62 assert.Nil(t, pool) 63 assert.Error(t, err) 64 } 65 66 func TestPoolAcquireCreatesResourceWhenNoneIdle(t *testing.T) { 67 constructor, _ := createConstructor() 68 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 69 require.NoError(t, err) 70 defer pool.Close() 71 72 res, err := pool.Acquire(context.Background()) 73 require.NoError(t, err) 74 assert.Equal(t, 1, res.Value()) 75 assert.WithinDuration(t, time.Now(), res.CreationTime(), time.Second) 76 res.Release() 77 } 78 79 func TestPoolAcquireCallsConstructorWithAcquireContextValuesButNotDeadline(t *testing.T) { 80 constructor := func(ctx context.Context) (int, error) { 81 if ctx.Value("test") != "from Acquire" { 82 return 0, errors.New("did not get value from Acquire") 83 } 84 if _, ok := ctx.Deadline(); ok { 85 return 0, errors.New("should not have gotten deadline from Acquire") 86 } 87 88 return 1, nil 89 } 90 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 91 require.NoError(t, err) 92 defer pool.Close() 93 94 ctx := context.WithValue(context.Background(), "test", "from Acquire") 95 ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 96 defer cancel() 97 res, err := pool.Acquire(ctx) 98 require.NoError(t, err) 99 assert.Equal(t, 1, res.Value()) 100 assert.WithinDuration(t, time.Now(), res.CreationTime(), time.Second) 101 res.Release() 102 } 103 104 func TestPoolAcquireCalledConstructorIsNotCanceledByAcquireCancellation(t *testing.T) { 105 constructor := func(ctx context.Context) (int, error) { 106 time.Sleep(100 * time.Millisecond) 107 return 1, nil 108 } 109 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 110 require.NoError(t, err) 111 defer pool.Close() 112 113 ctx, cancel := context.WithTimeout(context.Background(), 25*time.Millisecond) 114 defer cancel() 115 res, err := pool.Acquire(ctx) 116 assert.Nil(t, res) 117 assert.Equal(t, context.DeadlineExceeded, err) 118 119 time.Sleep(200 * time.Millisecond) 120 121 assert.EqualValues(t, 1, pool.Stat().TotalResources()) 122 assert.EqualValues(t, 1, pool.Stat().CanceledAcquireCount()) 123 } 124 125 func TestPoolAcquireDoesNotCreatesResourceWhenItWouldExceedMaxSize(t *testing.T) { 126 constructor, createCounter := createConstructor() 127 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 128 require.NoError(t, err) 129 130 wg := &sync.WaitGroup{} 131 132 for i := 0; i < 100; i++ { 133 wg.Add(1) 134 go func() { 135 for j := 0; j < 100; j++ { 136 res, err := pool.Acquire(context.Background()) 137 assert.NoError(t, err) 138 assert.Equal(t, 1, res.Value()) 139 res.Release() 140 } 141 wg.Done() 142 }() 143 } 144 145 wg.Wait() 146 147 assert.EqualValues(t, 1, createCounter.Value()) 148 assert.EqualValues(t, 1, pool.Stat().TotalResources()) 149 } 150 151 func TestPoolAcquireWithCancellableContext(t *testing.T) { 152 constructor, createCounter := createConstructor() 153 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 154 require.NoError(t, err) 155 156 wg := &sync.WaitGroup{} 157 158 for i := 0; i < 100; i++ { 159 wg.Add(1) 160 go func() { 161 for j := 0; j < 100; j++ { 162 ctx, cancel := context.WithCancel(context.Background()) 163 res, err := pool.Acquire(ctx) 164 assert.NoError(t, err) 165 assert.Equal(t, 1, res.Value()) 166 res.Release() 167 cancel() 168 } 169 wg.Done() 170 }() 171 } 172 173 wg.Wait() 174 175 assert.EqualValues(t, 1, createCounter.Value()) 176 assert.EqualValues(t, 1, pool.Stat().TotalResources()) 177 } 178 179 func TestPoolAcquireReturnsErrorFromFailedResourceCreate(t *testing.T) { 180 errCreateFailed := errors.New("create failed") 181 constructor := func(ctx context.Context) (int, error) { 182 return 0, errCreateFailed 183 } 184 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 185 require.NoError(t, err) 186 187 res, err := pool.Acquire(context.Background()) 188 assert.Equal(t, errCreateFailed, err) 189 assert.Nil(t, res) 190 } 191 192 func TestPoolAcquireCreatesResourceRespectingContext(t *testing.T) { 193 var cancel func() 194 constructor := func(ctx context.Context) (int, error) { 195 cancel() 196 // sleep to give a chance for the acquire to recognize it's cancelled 197 time.Sleep(10 * time.Millisecond) 198 return 1, nil 199 } 200 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 201 require.NoError(t, err) 202 defer pool.Close() 203 204 var ctx context.Context 205 ctx, cancel = context.WithCancel(context.Background()) 206 defer cancel() 207 _, err = pool.Acquire(ctx) 208 assert.ErrorIs(t, err, context.Canceled) 209 210 // wait for the constructor to sleep and then for the resource to be added back 211 // to the idle pool 212 time.Sleep(100 * time.Millisecond) 213 214 stat := pool.Stat() 215 assert.EqualValues(t, 1, stat.IdleResources()) 216 assert.EqualValues(t, 1, stat.TotalResources()) 217 } 218 219 func TestPoolAcquireReusesResources(t *testing.T) { 220 constructor, createCounter := createConstructor() 221 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 222 require.NoError(t, err) 223 224 res, err := pool.Acquire(context.Background()) 225 require.NoError(t, err) 226 assert.Equal(t, 1, res.Value()) 227 228 res.Release() 229 230 res, err = pool.Acquire(context.Background()) 231 require.NoError(t, err) 232 assert.Equal(t, 1, res.Value()) 233 234 res.Release() 235 236 assert.Equal(t, 1, createCounter.Value()) 237 } 238 239 func TestPoolTryAcquire(t *testing.T) { 240 constructor, createCounter := createConstructor() 241 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 242 require.NoError(t, err) 243 244 // Pool is initially empty so TryAcquire fails but starts construction of resource in the background. 245 res, err := pool.TryAcquire(context.Background()) 246 require.EqualError(t, err, puddle.ErrNotAvailable.Error()) 247 assert.Nil(t, res) 248 249 // Wait for background creation to complete. 250 time.Sleep(100 * time.Millisecond) 251 252 res, err = pool.TryAcquire(context.Background()) 253 require.NoError(t, err) 254 assert.Equal(t, 1, res.Value()) 255 defer res.Release() 256 257 res, err = pool.TryAcquire(context.Background()) 258 require.EqualError(t, err, puddle.ErrNotAvailable.Error()) 259 assert.Nil(t, res) 260 261 assert.Equal(t, 1, createCounter.Value()) 262 } 263 264 func TestPoolTryAcquireReturnsErrorWhenPoolIsClosed(t *testing.T) { 265 constructor, _ := createConstructor() 266 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 267 require.NoError(t, err) 268 pool.Close() 269 270 res, err := pool.TryAcquire(context.Background()) 271 assert.Equal(t, puddle.ErrClosedPool, err) 272 assert.Nil(t, res) 273 } 274 275 func TestPoolTryAcquireWithFailedResourceCreate(t *testing.T) { 276 errCreateFailed := errors.New("create failed") 277 constructor := func(ctx context.Context) (int, error) { 278 return 0, errCreateFailed 279 } 280 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 281 require.NoError(t, err) 282 283 res, err := pool.TryAcquire(context.Background()) 284 require.EqualError(t, err, puddle.ErrNotAvailable.Error()) 285 assert.Nil(t, res) 286 } 287 288 func TestPoolAcquireNilContextDoesNotLeavePoolLocked(t *testing.T) { 289 constructor, createCounter := createConstructor() 290 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 291 require.NoError(t, err) 292 293 assert.Panics(t, func() { pool.Acquire(nil) }) 294 295 res, err := pool.Acquire(context.Background()) 296 require.NoError(t, err) 297 assert.Equal(t, 1, res.Value()) 298 res.Release() 299 300 assert.Equal(t, 1, createCounter.Value()) 301 } 302 303 func TestPoolAcquireContextAlreadyCanceled(t *testing.T) { 304 constructor := func(ctx context.Context) (int, error) { 305 panic("should never be called") 306 } 307 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 308 require.NoError(t, err) 309 310 ctx, cancel := context.WithCancel(context.Background()) 311 cancel() 312 res, err := pool.Acquire(ctx) 313 assert.Equal(t, context.Canceled, err) 314 assert.Nil(t, res) 315 } 316 317 func TestPoolAcquireContextCanceledDuringCreate(t *testing.T) { 318 ctx, cancel := context.WithCancel(context.Background()) 319 time.AfterFunc(100*time.Millisecond, cancel) 320 timeoutChan := time.After(1 * time.Second) 321 322 var constructorCalls Counter 323 constructor := func(ctx context.Context) (int, error) { 324 select { 325 case <-ctx.Done(): 326 return 0, ctx.Err() 327 case <-timeoutChan: 328 } 329 return constructorCalls.Next(), nil 330 } 331 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 332 require.NoError(t, err) 333 334 res, err := pool.Acquire(ctx) 335 assert.Equal(t, context.Canceled, err) 336 assert.Nil(t, res) 337 } 338 339 func TestPoolAcquireAllIdle(t *testing.T) { 340 constructor, _ := createConstructor() 341 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 342 require.NoError(t, err) 343 defer pool.Close() 344 345 resources := make([]*puddle.Resource[int], 4) 346 347 resources[0], err = pool.Acquire(context.Background()) 348 require.NoError(t, err) 349 resources[1], err = pool.Acquire(context.Background()) 350 require.NoError(t, err) 351 resources[2], err = pool.Acquire(context.Background()) 352 require.NoError(t, err) 353 resources[3], err = pool.Acquire(context.Background()) 354 require.NoError(t, err) 355 356 assert.Len(t, pool.AcquireAllIdle(), 0) 357 358 resources[0].Release() 359 resources[3].Release() 360 361 assert.ElementsMatch(t, []*puddle.Resource[int]{resources[0], resources[3]}, pool.AcquireAllIdle()) 362 363 resources[0].Release() 364 resources[3].Release() 365 resources[1].Release() 366 resources[2].Release() 367 368 assert.ElementsMatch(t, resources, pool.AcquireAllIdle()) 369 370 resources[0].Release() 371 resources[1].Release() 372 resources[2].Release() 373 resources[3].Release() 374 } 375 376 func TestPoolAcquireAllIdleWhenClosedIsNil(t *testing.T) { 377 constructor, _ := createConstructor() 378 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 379 require.NoError(t, err) 380 pool.Close() 381 assert.Nil(t, pool.AcquireAllIdle()) 382 } 383 384 func TestPoolCreateResource(t *testing.T) { 385 constructor, counter := createConstructor() 386 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 387 require.NoError(t, err) 388 defer pool.Close() 389 390 err = pool.CreateResource(context.Background()) 391 require.NoError(t, err) 392 393 stats := pool.Stat() 394 assert.EqualValues(t, 1, stats.IdleResources()) 395 396 res, err := pool.Acquire(context.Background()) 397 require.NoError(t, err) 398 assert.Equal(t, counter.Value(), res.Value()) 399 assert.True(t, res.LastUsedNanotime() > 0, "should set LastUsedNanotime so that idle calculations can still work") 400 assert.Equal(t, 1, res.Value()) 401 assert.WithinDuration(t, time.Now(), res.CreationTime(), time.Second) 402 res.Release() 403 404 assert.EqualValues(t, 0, pool.Stat().EmptyAcquireCount(), "should have been a warm resource") 405 } 406 407 func TestPoolCreateResourceReturnsErrorFromFailedResourceCreate(t *testing.T) { 408 errCreateFailed := errors.New("create failed") 409 constructor := func(ctx context.Context) (int, error) { 410 return 0, errCreateFailed 411 } 412 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 413 require.NoError(t, err) 414 415 err = pool.CreateResource(context.Background()) 416 assert.Equal(t, errCreateFailed, err) 417 } 418 419 func TestPoolCreateResourceReturnsErrorWhenAlreadyClosed(t *testing.T) { 420 constructor, _ := createConstructor() 421 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 422 require.NoError(t, err) 423 pool.Close() 424 err = pool.CreateResource(context.Background()) 425 assert.Equal(t, puddle.ErrClosedPool, err) 426 } 427 428 func TestPoolCreateResourceReturnsErrorWhenClosedWhileCreatingResource(t *testing.T) { 429 // There is no way to guarantee the correct order of the pool being closed while the resource is being constructed. 430 // But these sleeps should make it extremely likely. (Ah, the lengths we go for 100% test coverage...) 431 constructor := func(ctx context.Context) (int, error) { 432 time.Sleep(500 * time.Millisecond) 433 return 123, nil 434 } 435 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 436 require.NoError(t, err) 437 438 acquireErrChan := make(chan error) 439 go func() { 440 err := pool.CreateResource(context.Background()) 441 acquireErrChan <- err 442 }() 443 444 time.Sleep(250 * time.Millisecond) 445 pool.Close() 446 447 err = <-acquireErrChan 448 assert.Equal(t, puddle.ErrClosedPool, err) 449 } 450 451 func TestPoolCreateResourceReturnsErrorWhenPoolFull(t *testing.T) { 452 constructor, _ := createConstructor() 453 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 2}) 454 require.NoError(t, err) 455 defer pool.Close() 456 457 err = pool.CreateResource(context.Background()) 458 require.NoError(t, err) 459 460 stats := pool.Stat() 461 assert.EqualValues(t, 1, stats.IdleResources()) 462 463 err = pool.CreateResource(context.Background()) 464 require.NoError(t, err) 465 466 stats = pool.Stat() 467 assert.EqualValues(t, 2, stats.IdleResources()) 468 469 err = pool.CreateResource(context.Background()) 470 require.Error(t, err) 471 } 472 473 func TestPoolCloseClosesAllIdleResources(t *testing.T) { 474 constructor, _ := createConstructor() 475 476 var destructorCalls Counter 477 destructor := func(int) { 478 destructorCalls.Next() 479 } 480 481 p, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: destructor, MaxSize: 10}) 482 require.NoError(t, err) 483 484 resources := make([]*puddle.Resource[int], 4) 485 for i := range resources { 486 var err error 487 resources[i], err = p.Acquire(context.Background()) 488 require.Nil(t, err) 489 } 490 491 for _, res := range resources { 492 res.Release() 493 } 494 495 p.Close() 496 497 assert.Equal(t, len(resources), destructorCalls.Value()) 498 } 499 500 func TestPoolCloseBlocksUntilAllResourcesReleasedAndClosed(t *testing.T) { 501 constructor, _ := createConstructor() 502 var destructorCalls Counter 503 destructor := func(int) { 504 destructorCalls.Next() 505 } 506 507 p, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: destructor, MaxSize: 10}) 508 require.NoError(t, err) 509 510 resources := make([]*puddle.Resource[int], 4) 511 for i := range resources { 512 var err error 513 resources[i], err = p.Acquire(context.Background()) 514 require.Nil(t, err) 515 } 516 517 for _, res := range resources { 518 go func(res *puddle.Resource[int]) { 519 time.Sleep(100 * time.Millisecond) 520 res.Release() 521 }(res) 522 } 523 524 p.Close() 525 assert.Equal(t, len(resources), destructorCalls.Value()) 526 } 527 528 func TestPoolCloseIsSafeToCallMultipleTimes(t *testing.T) { 529 constructor, _ := createConstructor() 530 531 p, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 532 require.NoError(t, err) 533 534 p.Close() 535 p.Close() 536 } 537 538 func TestPoolResetDestroysAllIdleResources(t *testing.T) { 539 constructor, _ := createConstructor() 540 541 var destructorCalls Counter 542 destructor := func(int) { 543 destructorCalls.Next() 544 } 545 546 p, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: destructor, MaxSize: 10}) 547 require.NoError(t, err) 548 549 resources := make([]*puddle.Resource[int], 4) 550 for i := range resources { 551 var err error 552 resources[i], err = p.Acquire(context.Background()) 553 require.Nil(t, err) 554 } 555 556 for _, res := range resources { 557 res.Release() 558 } 559 560 require.EqualValues(t, 4, p.Stat().TotalResources()) 561 p.Reset() 562 require.EqualValues(t, 0, p.Stat().TotalResources()) 563 564 // Destructors are called in the background. No way to know when they are all finished. 565 for i := 0; i < 100; i++ { 566 if destructorCalls.Value() == len(resources) { 567 break 568 } 569 time.Sleep(100 * time.Millisecond) 570 } 571 require.Equal(t, len(resources), destructorCalls.Value()) 572 573 p.Close() 574 } 575 576 func TestPoolResetDestroysCheckedOutResourcesOnReturn(t *testing.T) { 577 constructor, _ := createConstructor() 578 579 var destructorCalls Counter 580 destructor := func(int) { 581 destructorCalls.Next() 582 } 583 584 p, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: destructor, MaxSize: 10}) 585 require.NoError(t, err) 586 587 resources := make([]*puddle.Resource[int], 4) 588 for i := range resources { 589 var err error 590 resources[i], err = p.Acquire(context.Background()) 591 require.Nil(t, err) 592 } 593 594 require.EqualValues(t, 4, p.Stat().TotalResources()) 595 p.Reset() 596 require.EqualValues(t, 4, p.Stat().TotalResources()) 597 598 for _, res := range resources { 599 res.Release() 600 } 601 602 require.EqualValues(t, 0, p.Stat().TotalResources()) 603 604 // Destructors are called in the background. No way to know when they are all finished. 605 for i := 0; i < 100; i++ { 606 if destructorCalls.Value() == len(resources) { 607 break 608 } 609 time.Sleep(100 * time.Millisecond) 610 } 611 require.Equal(t, len(resources), destructorCalls.Value()) 612 613 p.Close() 614 } 615 616 func TestPoolStatResources(t *testing.T) { 617 startWaitChan := make(chan struct{}) 618 waitingChan := make(chan struct{}) 619 endWaitChan := make(chan struct{}) 620 621 var constructorCalls Counter 622 constructor := func(ctx context.Context) (int, error) { 623 select { 624 case <-startWaitChan: 625 close(waitingChan) 626 <-endWaitChan 627 default: 628 } 629 630 return constructorCalls.Next(), nil 631 } 632 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 633 require.NoError(t, err) 634 defer pool.Close() 635 636 resAcquired, err := pool.Acquire(context.Background()) 637 require.Nil(t, err) 638 639 close(startWaitChan) 640 go func() { 641 res, err := pool.Acquire(context.Background()) 642 require.Nil(t, err) 643 res.Release() 644 }() 645 <-waitingChan 646 stat := pool.Stat() 647 648 assert.EqualValues(t, 2, stat.TotalResources()) 649 assert.EqualValues(t, 1, stat.ConstructingResources()) 650 assert.EqualValues(t, 1, stat.AcquiredResources()) 651 assert.EqualValues(t, 0, stat.IdleResources()) 652 assert.EqualValues(t, 10, stat.MaxResources()) 653 654 resAcquired.Release() 655 656 stat = pool.Stat() 657 assert.EqualValues(t, 2, stat.TotalResources()) 658 assert.EqualValues(t, 1, stat.ConstructingResources()) 659 assert.EqualValues(t, 0, stat.AcquiredResources()) 660 assert.EqualValues(t, 1, stat.IdleResources()) 661 assert.EqualValues(t, 10, stat.MaxResources()) 662 663 close(endWaitChan) 664 } 665 666 func TestPoolStatSuccessfulAcquireCounters(t *testing.T) { 667 constructor, _ := createConstructor() 668 sleepConstructor := func(ctx context.Context) (int, error) { 669 // sleep to make sure we don't fail the AcquireDuration test 670 time.Sleep(time.Nanosecond) 671 return constructor(ctx) 672 } 673 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: sleepConstructor, Destructor: stubDestructor, MaxSize: 1}) 674 require.NoError(t, err) 675 defer pool.Close() 676 677 res, err := pool.Acquire(context.Background()) 678 require.NoError(t, err) 679 res.Release() 680 681 stat := pool.Stat() 682 assert.Equal(t, int64(1), stat.AcquireCount()) 683 assert.Equal(t, int64(1), stat.EmptyAcquireCount()) 684 assert.True(t, stat.AcquireDuration() > 0, "expected stat.AcquireDuration() > 0 but %v", stat.AcquireDuration()) 685 lastAcquireDuration := stat.AcquireDuration() 686 687 res, err = pool.Acquire(context.Background()) 688 require.NoError(t, err) 689 res.Release() 690 691 stat = pool.Stat() 692 assert.Equal(t, int64(2), stat.AcquireCount()) 693 assert.Equal(t, int64(1), stat.EmptyAcquireCount()) 694 assert.True(t, stat.AcquireDuration() > lastAcquireDuration) 695 lastAcquireDuration = stat.AcquireDuration() 696 697 wg := &sync.WaitGroup{} 698 for i := 0; i < 2; i++ { 699 wg.Add(1) 700 go func() { 701 res, err = pool.Acquire(context.Background()) 702 require.NoError(t, err) 703 time.Sleep(50 * time.Millisecond) 704 res.Release() 705 wg.Done() 706 }() 707 } 708 709 wg.Wait() 710 711 stat = pool.Stat() 712 assert.Equal(t, int64(4), stat.AcquireCount()) 713 assert.Equal(t, int64(2), stat.EmptyAcquireCount()) 714 assert.True(t, stat.AcquireDuration() > lastAcquireDuration) 715 lastAcquireDuration = stat.AcquireDuration() 716 } 717 718 func TestPoolStatCanceledAcquireBeforeStart(t *testing.T) { 719 constructor, _ := createConstructor() 720 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 721 require.NoError(t, err) 722 defer pool.Close() 723 724 ctx, cancel := context.WithCancel(context.Background()) 725 cancel() 726 _, err = pool.Acquire(ctx) 727 require.Equal(t, context.Canceled, err) 728 729 stat := pool.Stat() 730 assert.Equal(t, int64(0), stat.AcquireCount()) 731 assert.Equal(t, int64(1), stat.CanceledAcquireCount()) 732 } 733 734 func TestPoolStatCanceledAcquireDuringCreate(t *testing.T) { 735 constructor := func(ctx context.Context) (int, error) { 736 <-ctx.Done() 737 return 0, ctx.Err() 738 } 739 740 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 741 require.NoError(t, err) 742 defer pool.Close() 743 744 ctx, cancel := context.WithCancel(context.Background()) 745 time.AfterFunc(50*time.Millisecond, cancel) 746 _, err = pool.Acquire(ctx) 747 require.Equal(t, context.Canceled, err) 748 749 // sleep to give the constructor goroutine time to mark cancelled 750 time.Sleep(10 * time.Millisecond) 751 752 stat := pool.Stat() 753 assert.Equal(t, int64(0), stat.AcquireCount()) 754 assert.Equal(t, int64(1), stat.CanceledAcquireCount()) 755 } 756 757 func TestPoolStatCanceledAcquireDuringWait(t *testing.T) { 758 constructor, _ := createConstructor() 759 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 760 require.NoError(t, err) 761 defer pool.Close() 762 763 res, err := pool.Acquire(context.Background()) 764 require.Nil(t, err) 765 766 ctx, cancel := context.WithCancel(context.Background()) 767 time.AfterFunc(50*time.Millisecond, cancel) 768 _, err = pool.Acquire(ctx) 769 require.Equal(t, context.Canceled, err) 770 771 res.Release() 772 773 stat := pool.Stat() 774 assert.Equal(t, int64(1), stat.AcquireCount()) 775 assert.Equal(t, int64(1), stat.CanceledAcquireCount()) 776 } 777 778 func TestResourceHijackRemovesResourceFromPoolButDoesNotDestroy(t *testing.T) { 779 constructor, _ := createConstructor() 780 var destructorCalls Counter 781 destructor := func(int) { 782 destructorCalls.Next() 783 } 784 785 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: destructor, MaxSize: 10}) 786 require.NoError(t, err) 787 788 res, err := pool.Acquire(context.Background()) 789 require.NoError(t, err) 790 assert.Equal(t, 1, res.Value()) 791 792 res.Hijack() 793 794 assert.EqualValues(t, 0, pool.Stat().TotalResources()) 795 assert.EqualValues(t, 0, destructorCalls.Value()) 796 797 // Can still call Value, CreationTime and IdleDuration 798 res.Value() 799 res.CreationTime() 800 res.IdleDuration() 801 } 802 803 func TestResourceDestroyRemovesResourceFromPool(t *testing.T) { 804 constructor, _ := createConstructor() 805 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 806 require.NoError(t, err) 807 808 res, err := pool.Acquire(context.Background()) 809 require.NoError(t, err) 810 assert.Equal(t, 1, res.Value()) 811 812 assert.EqualValues(t, 1, pool.Stat().TotalResources()) 813 res.Destroy() 814 for i := 0; i < 1000; i++ { 815 if pool.Stat().TotalResources() == 0 { 816 break 817 } 818 time.Sleep(time.Millisecond) 819 } 820 821 assert.EqualValues(t, 0, pool.Stat().TotalResources()) 822 } 823 824 func TestResourceLastUsageTimeTracking(t *testing.T) { 825 constructor, _ := createConstructor() 826 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 1}) 827 require.NoError(t, err) 828 829 res, err := pool.Acquire(context.Background()) 830 require.NoError(t, err) 831 t1 := res.LastUsedNanotime() 832 res.Release() 833 834 // Greater than zero after initial usage 835 res, err = pool.Acquire(context.Background()) 836 require.NoError(t, err) 837 t2 := res.LastUsedNanotime() 838 d2 := res.IdleDuration() 839 assert.True(t, t2 > t1) 840 res.ReleaseUnused() 841 842 // ReleaseUnused does not update usage tracking 843 res, err = pool.Acquire(context.Background()) 844 require.NoError(t, err) 845 t3 := res.LastUsedNanotime() 846 d3 := res.IdleDuration() 847 assert.EqualValues(t, t2, t3) 848 assert.True(t, d3 > d2) 849 res.Release() 850 851 // Release does update usage tracking 852 res, err = pool.Acquire(context.Background()) 853 require.NoError(t, err) 854 t4 := res.LastUsedNanotime() 855 assert.True(t, t4 > t3) 856 res.Release() 857 } 858 859 func TestResourcePanicsOnUsageWhenNotAcquired(t *testing.T) { 860 constructor, _ := createConstructor() 861 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 862 require.NoError(t, err) 863 864 res, err := pool.Acquire(context.Background()) 865 require.NoError(t, err) 866 res.Release() 867 868 assert.PanicsWithValue(t, "tried to release resource that is not acquired", res.Release) 869 assert.PanicsWithValue(t, "tried to release resource that is not acquired", res.ReleaseUnused) 870 assert.PanicsWithValue(t, "tried to destroy resource that is not acquired", res.Destroy) 871 assert.PanicsWithValue(t, "tried to hijack resource that is not acquired", res.Hijack) 872 assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.Value() }) 873 assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.CreationTime() }) 874 assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.LastUsedNanotime() }) 875 assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.IdleDuration() }) 876 } 877 878 func TestPoolAcquireReturnsErrorWhenPoolIsClosed(t *testing.T) { 879 constructor, _ := createConstructor() 880 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 10}) 881 require.NoError(t, err) 882 pool.Close() 883 884 res, err := pool.Acquire(context.Background()) 885 assert.Equal(t, puddle.ErrClosedPool, err) 886 assert.Nil(t, res) 887 } 888 889 func TestSignalIsSentWhenResourceFailedToCreate(t *testing.T) { 890 var c Counter 891 constructor := func(context.Context) (a any, err error) { 892 if c.Next() == 2 { 893 return nil, errors.New("outage") 894 } 895 return 1, nil 896 } 897 destructor := func(value any) {} 898 899 pool, err := puddle.NewPool(&puddle.Config[any]{Constructor: constructor, Destructor: destructor, MaxSize: 10}) 900 require.NoError(t, err) 901 902 res1, err := pool.Acquire(context.Background()) 903 require.NoError(t, err) 904 905 var wg sync.WaitGroup 906 for i := 0; i < 2; i++ { 907 wg.Add(1) 908 go func(name string) { 909 defer wg.Done() 910 _, _ = pool.Acquire(context.Background()) 911 }(strconv.Itoa(i)) 912 } 913 914 // ensure that both goroutines above are waiting for condition variable signal 915 time.Sleep(500 * time.Millisecond) 916 res1.Destroy() 917 wg.Wait() 918 } 919 920 func stressTestDur(t testing.TB) time.Duration { 921 s := os.Getenv("STRESS_TEST_DURATION") 922 if s == "" { 923 s = "1s" 924 } 925 926 dur, err := time.ParseDuration(s) 927 require.Nil(t, err) 928 return dur 929 } 930 931 func TestStress(t *testing.T) { 932 constructor, _ := createConstructor() 933 var destructorCalls Counter 934 destructor := func(int) { 935 destructorCalls.Next() 936 } 937 938 poolSize := runtime.NumCPU() 939 if poolSize < 4 { 940 poolSize = 4 941 } 942 943 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: destructor, MaxSize: int32(poolSize)}) 944 require.NoError(t, err) 945 946 finishChan := make(chan struct{}) 947 wg := &sync.WaitGroup{} 948 949 releaseOrDestroyOrHijack := func(res *puddle.Resource[int]) { 950 n := rand.Intn(100) 951 if n < 5 { 952 res.Hijack() 953 destructor(res.Value()) 954 } else if n < 10 { 955 res.Destroy() 956 } else { 957 res.Release() 958 } 959 } 960 961 actions := []func(){ 962 // Acquire 963 func() { 964 res, err := pool.Acquire(context.Background()) 965 if err != nil { 966 if err != puddle.ErrClosedPool { 967 assert.Failf(t, "stress acquire", "pool.Acquire returned unexpected err: %v", err) 968 } 969 return 970 } 971 972 time.Sleep(time.Duration(rand.Int63n(100)) * time.Millisecond) 973 releaseOrDestroyOrHijack(res) 974 }, 975 // Acquire possibly canceled by context 976 func() { 977 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(rand.Int63n(2000))*time.Nanosecond) 978 defer cancel() 979 res, err := pool.Acquire(ctx) 980 if err != nil { 981 if err != puddle.ErrClosedPool && err != context.Canceled && err != context.DeadlineExceeded { 982 assert.Failf(t, "stress acquire possibly canceled by context", "pool.Acquire returned unexpected err: %v", err) 983 } 984 return 985 } 986 987 time.Sleep(time.Duration(rand.Int63n(2000)) * time.Nanosecond) 988 releaseOrDestroyOrHijack(res) 989 }, 990 // TryAcquire 991 func() { 992 res, err := pool.TryAcquire(context.Background()) 993 if err != nil { 994 if err != puddle.ErrClosedPool && err != puddle.ErrNotAvailable { 995 assert.Failf(t, "stress TryAcquire", "pool.TryAcquire returned unexpected err: %v", err) 996 } 997 return 998 } 999 1000 time.Sleep(time.Duration(rand.Int63n(100)) * time.Millisecond) 1001 releaseOrDestroyOrHijack(res) 1002 }, 1003 // AcquireAllIdle (though under heavy load this will almost certainly always get an empty slice) 1004 func() { 1005 resources := pool.AcquireAllIdle() 1006 for _, res := range resources { 1007 res.Release() 1008 } 1009 }, 1010 // Stat 1011 func() { 1012 stat := pool.Stat() 1013 assert.NotNil(t, stat) 1014 }, 1015 // CreateResource 1016 func() { 1017 err := pool.CreateResource(context.Background()) 1018 if err != nil && !errors.Is(err, puddle.ErrClosedPool) && !errors.Is(err, puddle.ErrNotAvailable) { 1019 t.Error(err) 1020 } 1021 }, 1022 } 1023 1024 workerCount := int(poolSize) * 2 1025 1026 for i := 0; i < workerCount; i++ { 1027 wg.Add(1) 1028 go func() { 1029 defer wg.Done() 1030 for { 1031 select { 1032 case <-finishChan: 1033 return 1034 default: 1035 } 1036 1037 actions[rand.Intn(len(actions))]() 1038 } 1039 }() 1040 } 1041 1042 time.AfterFunc(stressTestDur(t), func() { close(finishChan) }) 1043 wg.Wait() 1044 pool.Close() 1045 } 1046 1047 func TestStress_AcquireAllIdle_TryAcquire(t *testing.T) { 1048 r := require.New(t) 1049 1050 pool := testPool[int32](t) 1051 1052 var wg sync.WaitGroup 1053 done := make(chan struct{}) 1054 1055 wg.Add(1) 1056 go func() { 1057 defer wg.Done() 1058 1059 for { 1060 select { 1061 case <-done: 1062 return 1063 default: 1064 } 1065 1066 idleRes := pool.AcquireAllIdle() 1067 r.Less(len(idleRes), 2) 1068 for _, res := range idleRes { 1069 res.Release() 1070 } 1071 } 1072 }() 1073 1074 wg.Add(1) 1075 go func() { 1076 defer wg.Done() 1077 1078 for { 1079 select { 1080 case <-done: 1081 return 1082 default: 1083 } 1084 1085 res, err := pool.TryAcquire(context.Background()) 1086 if err != nil { 1087 r.Equal(puddle.ErrNotAvailable, err) 1088 } else { 1089 r.NotNil(res) 1090 res.Release() 1091 } 1092 } 1093 }() 1094 1095 time.AfterFunc(stressTestDur(t), func() { close(done) }) 1096 wg.Wait() 1097 } 1098 1099 func TestStress_AcquireAllIdle_Acquire(t *testing.T) { 1100 r := require.New(t) 1101 1102 pool := testPool[int32](t) 1103 1104 var wg sync.WaitGroup 1105 done := make(chan struct{}) 1106 1107 wg.Add(1) 1108 go func() { 1109 defer wg.Done() 1110 1111 for { 1112 select { 1113 case <-done: 1114 return 1115 default: 1116 } 1117 1118 idleRes := pool.AcquireAllIdle() 1119 r.Less(len(idleRes), 2) 1120 for _, res := range idleRes { 1121 r.NotNil(res) 1122 res.Release() 1123 } 1124 } 1125 }() 1126 1127 wg.Add(1) 1128 go func() { 1129 defer wg.Done() 1130 1131 for { 1132 select { 1133 case <-done: 1134 return 1135 default: 1136 } 1137 1138 res, err := pool.Acquire(context.Background()) 1139 if err != nil { 1140 r.Equal(puddle.ErrNotAvailable, err) 1141 } else { 1142 r.NotNil(res) 1143 res.Release() 1144 } 1145 } 1146 }() 1147 1148 time.AfterFunc(stressTestDur(t), func() { close(done) }) 1149 wg.Wait() 1150 } 1151 1152 func startAcceptOnceDummyServer(laddr string) { 1153 ln, err := net.Listen("tcp", laddr) 1154 if err != nil { 1155 log.Fatalln("Listen:", err) 1156 } 1157 1158 // Listen one time 1159 go func() { 1160 conn, err := ln.Accept() 1161 if err != nil { 1162 log.Fatalln("Accept:", err) 1163 } 1164 1165 for { 1166 buf := make([]byte, 1) 1167 _, err := conn.Read(buf) 1168 if err != nil { 1169 return 1170 } 1171 } 1172 }() 1173 1174 } 1175 1176 func ExamplePool() { 1177 // Dummy server 1178 laddr := "127.0.0.1:8080" 1179 startAcceptOnceDummyServer(laddr) 1180 1181 // Pool creation 1182 constructor := func(context.Context) (any, error) { 1183 return net.Dial("tcp", laddr) 1184 } 1185 destructor := func(value any) { 1186 value.(net.Conn).Close() 1187 } 1188 maxPoolSize := int32(10) 1189 1190 pool, err := puddle.NewPool(&puddle.Config[any]{Constructor: constructor, Destructor: destructor, MaxSize: int32(maxPoolSize)}) 1191 if err != nil { 1192 log.Fatalln("NewPool", err) 1193 } 1194 1195 // Use pool multiple times 1196 for i := 0; i < 10; i++ { 1197 // Acquire resource 1198 res, err := pool.Acquire(context.Background()) 1199 if err != nil { 1200 log.Fatalln("Acquire", err) 1201 } 1202 1203 // Type-assert value and use 1204 _, err = res.Value().(net.Conn).Write([]byte{1}) 1205 if err != nil { 1206 log.Fatalln("Write", err) 1207 } 1208 1209 // Release when done. 1210 res.Release() 1211 } 1212 1213 stats := pool.Stat() 1214 pool.Close() 1215 1216 fmt.Println("Connections:", stats.TotalResources()) 1217 fmt.Println("Acquires:", stats.AcquireCount()) 1218 // Output: 1219 // Connections: 1 1220 // Acquires: 10 1221 } 1222 1223 func BenchmarkPoolAcquireAndRelease(b *testing.B) { 1224 benchmarks := []struct { 1225 poolSize int32 1226 clientCount int 1227 cancellable bool 1228 }{ 1229 {8, 1, false}, 1230 {8, 2, false}, 1231 {8, 8, false}, 1232 {8, 32, false}, 1233 {8, 128, false}, 1234 {8, 512, false}, 1235 {8, 2048, false}, 1236 {8, 8192, false}, 1237 1238 {64, 2, false}, 1239 {64, 8, false}, 1240 {64, 32, false}, 1241 {64, 128, false}, 1242 {64, 512, false}, 1243 {64, 2048, false}, 1244 {64, 8192, false}, 1245 1246 {512, 2, false}, 1247 {512, 8, false}, 1248 {512, 32, false}, 1249 {512, 128, false}, 1250 {512, 512, false}, 1251 {512, 2048, false}, 1252 {512, 8192, false}, 1253 1254 {8, 2, true}, 1255 {8, 8, true}, 1256 {8, 32, true}, 1257 {8, 128, true}, 1258 {8, 512, true}, 1259 {8, 2048, true}, 1260 {8, 8192, true}, 1261 1262 {64, 2, true}, 1263 {64, 8, true}, 1264 {64, 32, true}, 1265 {64, 128, true}, 1266 {64, 512, true}, 1267 {64, 2048, true}, 1268 {64, 8192, true}, 1269 1270 {512, 2, true}, 1271 {512, 8, true}, 1272 {512, 32, true}, 1273 {512, 128, true}, 1274 {512, 512, true}, 1275 {512, 2048, true}, 1276 {512, 8192, true}, 1277 } 1278 1279 for _, bm := range benchmarks { 1280 name := fmt.Sprintf("PoolSize=%d/ClientCount=%d/Cancellable=%v", bm.poolSize, bm.clientCount, bm.cancellable) 1281 1282 b.Run(name, func(b *testing.B) { 1283 ctx := context.Background() 1284 cancel := func() {} 1285 if bm.cancellable { 1286 ctx, cancel = context.WithCancel(ctx) 1287 } 1288 1289 wg := &sync.WaitGroup{} 1290 1291 constructor, _ := createConstructor() 1292 pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: bm.poolSize}) 1293 if err != nil { 1294 b.Fatal(err) 1295 } 1296 1297 for i := 0; i < bm.clientCount; i++ { 1298 wg.Add(1) 1299 go func() { 1300 defer wg.Done() 1301 1302 for j := 0; j < b.N; j++ { 1303 res, err := pool.Acquire(ctx) 1304 if err != nil { 1305 b.Fatal(err) 1306 } 1307 res.Release() 1308 } 1309 }() 1310 } 1311 1312 wg.Wait() 1313 cancel() 1314 }) 1315 } 1316 } 1317 1318 func TestAcquireAllSem(t *testing.T) { 1319 r := require.New(t) 1320 1321 sem := semaphore.NewWeighted(5) 1322 r.Equal(4, puddle.AcquireSemAll(sem, 4)) 1323 sem.Release(4) 1324 1325 r.Equal(5, puddle.AcquireSemAll(sem, 5)) 1326 sem.Release(5) 1327 1328 r.Equal(5, puddle.AcquireSemAll(sem, 6)) 1329 sem.Release(5) 1330 } 1331 1332 func testPool[T any](t testing.TB) *puddle.Pool[T] { 1333 cfg := puddle.Config[T]{ 1334 MaxSize: 1, 1335 Constructor: func(ctx context.Context) (T, error) { 1336 var zero T 1337 return zero, nil 1338 }, 1339 Destructor: func(T) {}, 1340 } 1341 1342 pool, err := puddle.NewPool(&cfg) 1343 require.NoError(t, err) 1344 t.Cleanup(pool.Close) 1345 1346 return pool 1347 } 1348 1349 func releaser[T any](t testing.TB) chan<- *puddle.Resource[T] { 1350 startChan := make(chan struct{}) 1351 workChan := make(chan *puddle.Resource[T], 1) 1352 1353 go func() { 1354 close(startChan) 1355 1356 for r := range workChan { 1357 r.Release() 1358 } 1359 }() 1360 t.Cleanup(func() { close(workChan) }) 1361 1362 // Wait for goroutine start. 1363 <-startChan 1364 return workChan 1365 } 1366 1367 func TestReleaseAfterAcquire(t *testing.T) { 1368 const cnt = 100000 1369 1370 r := require.New(t) 1371 ctx := context.Background() 1372 pool := testPool[int32](t) 1373 releaseChan := releaser[int32](t) 1374 1375 res, err := pool.Acquire(ctx) 1376 r.NoError(err) 1377 // We need to release the last connection. Otherwise the pool.Close() 1378 // method will block and this function will never return. 1379 defer func() { res.Release() }() 1380 1381 for i := 0; i < cnt; i++ { 1382 releaseChan <- res 1383 res, err = pool.Acquire(ctx) 1384 r.NoError(err) 1385 } 1386 } 1387 1388 func BenchmarkAcquire_ReleaseAfterAcquire(b *testing.B) { 1389 r := require.New(b) 1390 ctx := context.Background() 1391 pool := testPool[int32](b) 1392 releaseChan := releaser[int32](b) 1393 1394 res, err := pool.Acquire(ctx) 1395 r.NoError(err) 1396 // We need to release the last connection. Otherwise the pool.Close() 1397 // method will block and this function will never return. 1398 defer func() { res.Release() }() 1399 1400 b.ResetTimer() 1401 for i := 0; i < b.N; i++ { 1402 releaseChan <- res 1403 res, err = pool.Acquire(ctx) 1404 r.NoError(err) 1405 } 1406 } 1407 1408 func withCPULoad() { 1409 // Multiply by 2 to similate overload of the system. 1410 numGoroutines := runtime.NumCPU() * 2 1411 1412 var wg sync.WaitGroup 1413 for i := 0; i < numGoroutines; i++ { 1414 wg.Add(1) 1415 go func() { 1416 wg.Done() 1417 1418 // Similate computationally intensive task. 1419 for j := 0; true; j++ { 1420 } 1421 }() 1422 } 1423 1424 wg.Wait() 1425 } 1426 1427 func BenchmarkAcquire_ReleaseAfterAcquireWithCPULoad(b *testing.B) { 1428 r := require.New(b) 1429 ctx := context.Background() 1430 pool := testPool[int32](b) 1431 releaseChan := releaser[int32](b) 1432 1433 withCPULoad() 1434 1435 res, err := pool.Acquire(ctx) 1436 r.NoError(err) 1437 // We need to release the last connection. Otherwise the pool.Close() 1438 // method will block and this function will never return. 1439 defer func() { res.Release() }() 1440 1441 b.ResetTimer() 1442 for i := 0; i < b.N; i++ { 1443 releaseChan <- res 1444 res, err = pool.Acquire(ctx) 1445 r.NoError(err) 1446 } 1447 } 1448 1449 func BenchmarkAcquire_MultipleCancelled(b *testing.B) { 1450 const cancelCnt = 64 1451 1452 r := require.New(b) 1453 ctx := context.Background() 1454 pool := testPool[int32](b) 1455 releaseChan := releaser[int32](b) 1456 1457 cancelCtx, cancel := context.WithCancel(ctx) 1458 cancel() 1459 1460 res, err := pool.Acquire(ctx) 1461 r.NoError(err) 1462 // We need to release the last connection. Otherwise the pool.Close() 1463 // method will block and this function will never return. 1464 defer func() { res.Release() }() 1465 1466 b.ResetTimer() 1467 for i := 0; i < b.N; i++ { 1468 for j := 0; j < cancelCnt; j++ { 1469 _, err = pool.AcquireRaw(cancelCtx) 1470 r.Equal(context.Canceled, err) 1471 } 1472 1473 releaseChan <- res 1474 res, err = pool.Acquire(ctx) 1475 r.NoError(err) 1476 } 1477 } 1478 1479 func BenchmarkAcquire_MultipleCancelledWithCPULoad(b *testing.B) { 1480 const cancelCnt = 3 1481 1482 r := require.New(b) 1483 ctx := context.Background() 1484 pool := testPool[int32](b) 1485 releaseChan := releaser[int32](b) 1486 1487 cancelCtx, cancel := context.WithCancel(ctx) 1488 cancel() 1489 1490 withCPULoad() 1491 1492 res, err := pool.Acquire(ctx) 1493 r.NoError(err) 1494 // We need to release the last connection. Otherwise the pool.Close() 1495 // method will block and this function will never return. 1496 defer func() { res.Release() }() 1497 1498 b.ResetTimer() 1499 for i := 0; i < b.N; i++ { 1500 for j := 0; j < cancelCnt; j++ { 1501 _, err = pool.AcquireRaw(cancelCtx) 1502 r.Equal(context.Canceled, err) 1503 } 1504 1505 releaseChan <- res 1506 res, err = pool.Acquire(ctx) 1507 r.NoError(err) 1508 } 1509 }