github.com/XiaoMi/Gaea@v1.2.5/util/resource_pool_test.go (about) 1 /* 2 Copyright 2017 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package util 18 19 import ( 20 "context" 21 "errors" 22 "testing" 23 "time" 24 25 "github.com/XiaoMi/Gaea/util/sync2" 26 ) 27 28 var lastID, count sync2.AtomicInt64 29 30 type TestResource struct { 31 num int64 32 closed bool 33 } 34 35 func (tr *TestResource) Close() { 36 if !tr.closed { 37 count.Add(-1) 38 tr.closed = true 39 } 40 } 41 42 func PoolFactory() (Resource, error) { 43 count.Add(1) 44 return &TestResource{lastID.Add(1), false}, nil 45 } 46 47 func FailFactory() (Resource, error) { 48 return nil, errors.New("Failed") 49 } 50 51 func SlowFailFactory() (Resource, error) { 52 time.Sleep(10 * time.Millisecond) 53 return nil, errors.New("Failed") 54 } 55 56 func TestOpen(t *testing.T) { 57 ctx := context.Background() 58 lastID.Set(0) 59 count.Set(0) 60 p := NewResourcePool(PoolFactory, 6, 6, time.Second) 61 p.SetDynamic(false) 62 p.ScaleCapacity(5) 63 var resources [10]Resource 64 65 // Test Get 66 for i := 0; i < 5; i++ { 67 r, err := p.Get(ctx) 68 resources[i] = r 69 if err != nil { 70 t.Errorf("Unexpected error %v", err) 71 } 72 if p.Available() != int64(5-i-1) { 73 t.Errorf("expecting %d, received %d", 5-i-1, p.Available()) 74 } 75 if p.WaitCount() != 0 { 76 t.Errorf("expecting 0, received %d", p.WaitCount()) 77 } 78 if p.WaitTime() != 0 { 79 t.Errorf("expecting 0, received %d", p.WaitTime()) 80 } 81 if lastID.Get() != int64(i+1) { 82 t.Errorf("Expecting %d, received %d", i+1, lastID.Get()) 83 } 84 if count.Get() != int64(i+1) { 85 t.Errorf("Expecting %d, received %d", i+1, count.Get()) 86 } 87 } 88 89 // Test that Get waits 90 ch := make(chan bool) 91 go func() { 92 for i := 0; i < 5; i++ { 93 r, err := p.Get(ctx) 94 if err != nil { 95 t.Errorf("Get failed: %v", err) 96 } 97 resources[i] = r 98 } 99 for i := 0; i < 5; i++ { 100 p.Put(resources[i]) 101 } 102 ch <- true 103 }() 104 for i := 0; i < 5; i++ { 105 // Sleep to ensure the goroutine waits 106 time.Sleep(10 * time.Millisecond) 107 p.Put(resources[i]) 108 } 109 <-ch 110 if p.WaitCount() != 5 { 111 t.Errorf("Expecting 5, received %d", p.WaitCount()) 112 } 113 if p.WaitTime() == 0 { 114 t.Errorf("Expecting non-zero") 115 } 116 if lastID.Get() != 5 { 117 t.Errorf("Expecting 5, received %d", lastID.Get()) 118 } 119 120 // Test Close resource 121 r, err := p.Get(ctx) 122 if err != nil { 123 t.Errorf("Unexpected error %v", err) 124 } 125 r.Close() 126 p.Put(nil) 127 if count.Get() != 4 { 128 t.Errorf("Expecting 4, received %d", count.Get()) 129 } 130 for i := 0; i < 5; i++ { 131 r, err := p.Get(ctx) 132 if err != nil { 133 t.Errorf("Get failed: %v", err) 134 } 135 resources[i] = r 136 } 137 for i := 0; i < 5; i++ { 138 p.Put(resources[i]) 139 } 140 if count.Get() != 5 { 141 t.Errorf("Expecting 5, received %d", count.Get()) 142 } 143 if lastID.Get() != 6 { 144 t.Errorf("Expecting 6, received %d", lastID.Get()) 145 } 146 147 // ScaleCapacity 148 p.ScaleCapacity(3) 149 if count.Get() != 3 { 150 t.Errorf("Expecting 3, received %d", count.Get()) 151 } 152 if lastID.Get() != 6 { 153 t.Errorf("Expecting 6, received %d", lastID.Get()) 154 } 155 if p.Capacity() != 3 { 156 t.Errorf("Expecting 3, received %d", p.Capacity()) 157 } 158 if p.Available() != 3 { 159 t.Errorf("Expecting 3, received %d", p.Available()) 160 } 161 p.ScaleCapacity(6) 162 if p.Capacity() != 6 { 163 t.Errorf("Expecting 6, received %d", p.Capacity()) 164 } 165 if p.Available() != 6 { 166 t.Errorf("Expecting 6, received %d", p.Available()) 167 } 168 for i := 0; i < 6; i++ { 169 r, err := p.Get(ctx) 170 if err != nil { 171 t.Errorf("Get failed: %v", err) 172 } 173 resources[i] = r 174 } 175 for i := 0; i < 6; i++ { 176 p.Put(resources[i]) 177 } 178 if count.Get() != 6 { 179 t.Errorf("Expecting 5, received %d", count.Get()) 180 } 181 if lastID.Get() != 9 { 182 t.Errorf("Expecting 9, received %d", lastID.Get()) 183 } 184 185 // Close 186 p.Close() 187 if p.Capacity() != 0 { 188 t.Errorf("Expecting 0, received %d", p.Capacity()) 189 } 190 if p.Available() != 0 { 191 t.Errorf("Expecting 0, received %d", p.Available()) 192 } 193 if count.Get() != 0 { 194 t.Errorf("Expecting 0, received %d", count.Get()) 195 } 196 } 197 198 func TestOpenDynamic(t *testing.T) { 199 ctx := context.Background() 200 lastID.Set(0) 201 count.Set(0) 202 p := NewResourcePool(PoolFactory, 6, 10, time.Second) 203 p.ScaleCapacity(5) 204 p.SetDynamic(true) 205 var resources [10]Resource 206 207 // Test Get 208 for i := 0; i < 7; i++ { 209 r, err := p.Get(ctx) 210 resources[i] = r 211 if err != nil { 212 t.Errorf("Unexpected error %v", err) 213 } 214 if i < 5 { 215 if p.Available() != int64(5-i-1) { 216 t.Errorf("expecting %d, received %d", 5-i-1, p.Available()) 217 } 218 } else { 219 if p.Available() != 0 { 220 t.Errorf("expecting %d, received %d", 0, p.Available()) 221 } 222 } 223 224 if p.WaitCount() != 0 { 225 t.Errorf("expecting 0, received %d", p.WaitCount()) 226 } 227 if p.WaitTime() != 0 { 228 t.Errorf("expecting 0, received %d", p.WaitTime()) 229 } 230 if lastID.Get() != int64(i+1) { 231 t.Errorf("Expecting %d, received %d", i+1, lastID.Get()) 232 } 233 if count.Get() != int64(i+1) { 234 t.Errorf("Expecting %d, received %d", i+1, count.Get()) 235 } 236 } 237 238 // Test that Get waits 239 ch := make(chan bool) 240 go func() { 241 for i := 0; i < 7; i++ { 242 r, err := p.Get(ctx) 243 if err != nil { 244 t.Errorf("Get failed: %v", err) 245 } 246 resources[i] = r 247 } 248 for i := 0; i < 7; i++ { 249 p.Put(resources[i]) 250 } 251 ch <- true 252 }() 253 for i := 0; i < 7; i++ { 254 // Sleep to ensure the goroutine waits 255 time.Sleep(10 * time.Millisecond) 256 p.Put(resources[i]) 257 } 258 <-ch 259 if p.WaitCount() != 4 { 260 t.Errorf("Expecting 4, received %d", p.WaitCount()) 261 } 262 if p.WaitTime() == 0 { 263 t.Errorf("Expecting non-zero") 264 } 265 if lastID.Get() != 10 { 266 t.Errorf("Expecting 10, received %d", lastID.Get()) 267 } 268 269 // Test Close resource 270 r, err := p.Get(ctx) 271 if err != nil { 272 t.Errorf("Unexpected error %v", err) 273 } 274 r.Close() 275 p.Put(nil) 276 if count.Get() != 9 { 277 t.Errorf("Expecting 9, received %d", count.Get()) 278 } 279 for i := 0; i < 5; i++ { 280 r, err := p.Get(ctx) 281 if err != nil { 282 t.Errorf("Get failed: %v", err) 283 } 284 resources[i] = r 285 } 286 for i := 0; i < 5; i++ { 287 p.Put(resources[i]) 288 } 289 if count.Get() != 9 { 290 t.Errorf("Expecting 9, received %d", count.Get()) 291 } 292 if lastID.Get() != 10 { 293 t.Errorf("Expecting 10, received %d", lastID.Get()) 294 } 295 } 296 297 func TestShrinking(t *testing.T) { 298 ctx := context.Background() 299 lastID.Set(0) 300 count.Set(0) 301 p := NewResourcePool(PoolFactory, 5, 5, time.Second) 302 p.SetDynamic(false) 303 var resources [10]Resource 304 // Leave one empty slot in the pool 305 for i := 0; i < 4; i++ { 306 r, err := p.Get(ctx) 307 if err != nil { 308 t.Errorf("Get failed: %v", err) 309 } 310 resources[i] = r 311 } 312 done := make(chan bool) 313 go func() { 314 p.ScaleCapacity(3) 315 done <- true 316 }() 317 expected := `{"Capacity": 3, "Available": 0, "Active": 4, "InUse": 4, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` 318 for i := 0; i < 10; i++ { 319 time.Sleep(10 * time.Millisecond) 320 stats := p.StatsJSON() 321 if stats != expected { 322 if i == 9 { 323 t.Errorf(`expecting '%s', received '%s'`, expected, stats) 324 } 325 } 326 } 327 // There are already 2 resources available in the pool. 328 // So, returning one should be enough for ScaleCapacity to complete. 329 p.Put(resources[3]) 330 <-done 331 // Return the rest of the resources 332 for i := 0; i < 3; i++ { 333 p.Put(resources[i]) 334 } 335 stats := p.StatsJSON() 336 expected = `{"Capacity": 3, "Available": 3, "Active": 3, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` 337 if stats != expected { 338 t.Errorf(`expecting '%s', received '%s'`, expected, stats) 339 } 340 if count.Get() != 3 { 341 t.Errorf("Expecting 3, received %d", count.Get()) 342 } 343 344 // Ensure no deadlock if ScaleCapacity is called after we start 345 // waiting for a resource 346 var err error 347 for i := 0; i < 3; i++ { 348 resources[i], err = p.Get(ctx) 349 if err != nil { 350 t.Errorf("Unexpected error %v", err) 351 } 352 } 353 // This will wait because pool is empty 354 go func() { 355 r, err := p.Get(ctx) 356 if err != nil { 357 t.Errorf("Unexpected error %v", err) 358 } 359 p.Put(r) 360 done <- true 361 }() 362 363 // This will also wait 364 go func() { 365 p.ScaleCapacity(2) 366 done <- true 367 }() 368 time.Sleep(10 * time.Millisecond) 369 370 // This should not hang 371 for i := 0; i < 3; i++ { 372 p.Put(resources[i]) 373 } 374 <-done 375 <-done 376 if p.Capacity() != 2 { 377 t.Errorf("Expecting 2, received %d", p.Capacity()) 378 } 379 if p.Available() != 2 { 380 t.Errorf("Expecting 2, received %d", p.Available()) 381 } 382 if p.WaitCount() != 1 { 383 t.Errorf("Expecting 1, received %d", p.WaitCount()) 384 } 385 if count.Get() != 2 { 386 t.Errorf("Expecting 2, received %d", count.Get()) 387 } 388 389 // Test race condition of ScaleCapacity with itself 390 p.ScaleCapacity(3) 391 for i := 0; i < 3; i++ { 392 resources[i], err = p.Get(ctx) 393 if err != nil { 394 t.Errorf("Unexpected error %v", err) 395 } 396 } 397 // This will wait because pool is empty 398 go func() { 399 r, err := p.Get(ctx) 400 if err != nil { 401 t.Errorf("Unexpected error %v", err) 402 } 403 p.Put(r) 404 done <- true 405 }() 406 time.Sleep(10 * time.Millisecond) 407 408 // This will wait till we Put 409 go p.ScaleCapacity(2) 410 time.Sleep(10 * time.Millisecond) 411 go p.ScaleCapacity(4) 412 time.Sleep(10 * time.Millisecond) 413 414 // This should not hang 415 for i := 0; i < 3; i++ { 416 p.Put(resources[i]) 417 } 418 <-done 419 420 err = p.ScaleCapacity(-1) 421 if err == nil { 422 t.Errorf("Expecting error") 423 } 424 err = p.ScaleCapacity(255555) 425 if err == nil { 426 t.Errorf("Expecting error") 427 } 428 429 if p.Capacity() != 4 { 430 t.Errorf("Expecting 4, received %d", p.Capacity()) 431 } 432 if p.Available() != 4 { 433 t.Errorf("Expecting 4, received %d", p.Available()) 434 } 435 } 436 437 func TestClosing(t *testing.T) { 438 ctx := context.Background() 439 lastID.Set(0) 440 count.Set(0) 441 p := NewResourcePool(PoolFactory, 5, 5, time.Second) 442 p.SetDynamic(false) 443 var resources [10]Resource 444 for i := 0; i < 5; i++ { 445 r, err := p.Get(ctx) 446 if err != nil { 447 t.Errorf("Get failed: %v", err) 448 } 449 resources[i] = r 450 } 451 ch := make(chan bool) 452 go func() { 453 p.Close() 454 ch <- true 455 }() 456 457 // Wait for goroutine to call Close 458 time.Sleep(10 * time.Millisecond) 459 stats := p.StatsJSON() 460 expected := `{"Capacity": 0, "Available": 0, "Active": 5, "InUse": 5, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` 461 if stats != expected { 462 t.Errorf(`expecting '%s', received '%s'`, expected, stats) 463 } 464 465 // Put is allowed when closing 466 for i := 0; i < 5; i++ { 467 p.Put(resources[i]) 468 } 469 470 // Wait for Close to return 471 <-ch 472 473 // ScaleCapacity must be ignored after Close 474 err := p.ScaleCapacity(1) 475 if err == nil { 476 t.Errorf("expecting error") 477 } 478 479 stats = p.StatsJSON() 480 expected = `{"Capacity": 0, "Available": 0, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` 481 if stats != expected { 482 t.Errorf(`expecting '%s', received '%s'`, expected, stats) 483 } 484 if lastID.Get() != 5 { 485 t.Errorf("Expecting 5, received %d", count.Get()) 486 } 487 if count.Get() != 0 { 488 t.Errorf("Expecting 0, received %d", count.Get()) 489 } 490 } 491 492 func TestIdleTimeout(t *testing.T) { 493 ctx := context.Background() 494 lastID.Set(0) 495 count.Set(0) 496 p := NewResourcePool(PoolFactory, 1, 1, 10*time.Millisecond) 497 p.SetDynamic(false) 498 defer p.Close() 499 500 r, err := p.Get(ctx) 501 if err != nil { 502 t.Errorf("Unexpected error %v", err) 503 } 504 if count.Get() != 1 { 505 t.Errorf("Expecting 1, received %d", count.Get()) 506 } 507 if p.IdleClosed() != 0 { 508 t.Errorf("Expecting 0, received %d", p.IdleClosed()) 509 } 510 p.Put(r) 511 if lastID.Get() != 1 { 512 t.Errorf("Expecting 1, received %d", count.Get()) 513 } 514 if count.Get() != 1 { 515 t.Errorf("Expecting 1, received %d", count.Get()) 516 } 517 if p.IdleClosed() != 0 { 518 t.Errorf("Expecting 0, received %d", p.IdleClosed()) 519 } 520 time.Sleep(20 * time.Millisecond) 521 522 if count.Get() != 0 { 523 t.Errorf("Expecting 0, received %d", count.Get()) 524 } 525 if p.IdleClosed() != 1 { 526 t.Errorf("Expecting 1, received %d", p.IdleClosed()) 527 } 528 r, err = p.Get(ctx) 529 if err != nil { 530 t.Errorf("Unexpected error %v", err) 531 } 532 if lastID.Get() != 2 { 533 t.Errorf("Expecting 2, received %d", count.Get()) 534 } 535 if count.Get() != 1 { 536 t.Errorf("Expecting 1, received %d", count.Get()) 537 } 538 if p.IdleClosed() != 1 { 539 t.Errorf("Expecting 1, received %d", p.IdleClosed()) 540 } 541 542 // sleep to let the idle closer run while all resources are in use 543 // then make sure things are still as we expect 544 time.Sleep(20 * time.Millisecond) 545 if lastID.Get() != 2 { 546 t.Errorf("Expecting 2, received %d", count.Get()) 547 } 548 if count.Get() != 1 { 549 t.Errorf("Expecting 1, received %d", count.Get()) 550 } 551 if p.IdleClosed() != 1 { 552 t.Errorf("Expecting 1, received %d", p.IdleClosed()) 553 } 554 p.Put(r) 555 r, err = p.Get(ctx) 556 if err != nil { 557 t.Errorf("Unexpected error %v", err) 558 } 559 if lastID.Get() != 2 { 560 t.Errorf("Expecting 2, received %d", count.Get()) 561 } 562 if count.Get() != 1 { 563 t.Errorf("Expecting 1, received %d", count.Get()) 564 } 565 if p.IdleClosed() != 1 { 566 t.Errorf("Expecting 1, received %d", p.IdleClosed()) 567 } 568 569 // the idle close thread wakes up every 1/100 of the idle time, so ensure 570 // the timeout change applies to newly added resources 571 p.SetIdleTimeout(1000 * time.Millisecond) 572 p.Put(r) 573 574 time.Sleep(20 * time.Millisecond) 575 if lastID.Get() != 2 { 576 t.Errorf("Expecting 2, received %d", count.Get()) 577 } 578 if count.Get() != 1 { 579 t.Errorf("Expecting 1, received %d", count.Get()) 580 } 581 if p.IdleClosed() != 1 { 582 t.Errorf("Expecting 1, received %d", p.IdleClosed()) 583 } 584 585 p.SetIdleTimeout(10 * time.Millisecond) 586 time.Sleep(20 * time.Millisecond) 587 if lastID.Get() != 2 { 588 t.Errorf("Expecting 2, received %d", count.Get()) 589 } 590 if count.Get() != 0 { 591 t.Errorf("Expecting 1, received %d", count.Get()) 592 } 593 if p.IdleClosed() != 2 { 594 t.Errorf("Expecting 2, received %d", p.IdleClosed()) 595 } 596 } 597 598 func TestCreateFail(t *testing.T) { 599 ctx := context.Background() 600 lastID.Set(0) 601 count.Set(0) 602 p := NewResourcePool(FailFactory, 5, 5, time.Second) 603 p.SetDynamic(false) 604 defer p.Close() 605 if _, err := p.Get(ctx); err.Error() != "Failed" { 606 t.Errorf("Expecting Failed, received %v", err) 607 } 608 stats := p.StatsJSON() 609 expected := `{"Capacity": 5, "Available": 5, "Active": 0, "InUse": 0, "MaxCapacity": 5, "WaitCount": 0, "WaitTime": 0, "IdleTimeout": 1000000000, "IdleClosed": 0}` 610 if stats != expected { 611 t.Errorf(`expecting '%s', received '%s'`, expected, stats) 612 } 613 } 614 615 func TestSlowCreateFail(t *testing.T) { 616 ctx := context.Background() 617 lastID.Set(0) 618 count.Set(0) 619 p := NewResourcePool(SlowFailFactory, 2, 2, time.Second) 620 p.SetDynamic(false) 621 defer p.Close() 622 ch := make(chan bool) 623 // The third Get should not wait indefinitely 624 for i := 0; i < 3; i++ { 625 go func() { 626 p.Get(ctx) 627 ch <- true 628 }() 629 } 630 for i := 0; i < 3; i++ { 631 <-ch 632 } 633 if p.Available() != 2 { 634 t.Errorf("Expecting 2, received %d", p.Available()) 635 } 636 } 637 638 func TestTimeout(t *testing.T) { 639 ctx := context.Background() 640 lastID.Set(0) 641 count.Set(0) 642 p := NewResourcePool(PoolFactory, 1, 1, time.Second) 643 p.SetDynamic(false) 644 defer p.Close() 645 r, err := p.Get(ctx) 646 if err != nil { 647 t.Fatal(err) 648 } 649 newctx, cancel := context.WithTimeout(ctx, 1*time.Millisecond) 650 _, err = p.Get(newctx) 651 cancel() 652 want := "resource pool timed out" 653 if err == nil || err.Error() != want { 654 t.Errorf("got %v, want %s", err, want) 655 } 656 p.Put(r) 657 } 658 659 func TestExpired(t *testing.T) { 660 lastID.Set(0) 661 count.Set(0) 662 p := NewResourcePool(PoolFactory, 1, 1, time.Second) 663 p.SetDynamic(false) 664 defer p.Close() 665 ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-1*time.Second)) 666 r, err := p.Get(ctx) 667 if err == nil { 668 p.Put(r) 669 } 670 cancel() 671 want := "resource pool timed out" 672 if err == nil || err.Error() != want { 673 t.Errorf("got %v, want %s", err, want) 674 } 675 }