github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/lease/manager_async_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lease_test 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/worker.v1" 17 "gopkg.in/juju/worker.v1/workertest" 18 19 corelease "github.com/juju/juju/core/lease" 20 coretesting "github.com/juju/juju/testing" 21 "github.com/juju/juju/worker/lease" 22 ) 23 24 type leaseMap = map[corelease.Key]corelease.Info 25 26 // AsyncSuite checks that expiries and claims that block don't prevent 27 // subsequent updates. 28 type AsyncSuite struct { 29 testing.IsolationSuite 30 } 31 32 var _ = gc.Suite(&AsyncSuite{}) 33 34 func (s *AsyncSuite) SetUpTest(c *gc.C) { 35 s.IsolationSuite.SetUpTest(c) 36 logger := loggo.GetLogger("juju.worker.lease") 37 logger.SetLogLevel(loggo.TRACE) 38 logger = loggo.GetLogger("lease_test") 39 logger.SetLogLevel(loggo.TRACE) 40 } 41 42 func (s *AsyncSuite) TestExpirySlow(c *gc.C) { 43 // TODO: jam 2019-02-05 This test is explicitly testing behavior that we are changing. 44 // It test that even if an Expire is slow, it doesn't block a follow up expire 45 // to trigger on the main loop. But *that* behavior means we can have runaway 46 // expiration loops if they are having problems. 47 // If we change our minds, we should reintroduce this test. 48 c.Skip("design change in behavior for Expire triggers") 49 // Ensure that even if an expiry is taking a long time, another 50 // expiry after it can still work. 51 52 slowStarted := make(chan struct{}) 53 slowFinish := make(chan struct{}) 54 55 quickFinished := make(chan struct{}) 56 57 fix := Fixture{ 58 leases: leaseMap{ 59 key("thing1"): { 60 Holder: "holden", 61 Expiry: offset(-time.Second), 62 }, 63 key("thing2"): { 64 Holder: "miller", 65 Expiry: offset(time.Second), 66 }, 67 }, 68 69 expectCalls: []call{{ 70 method: "Refresh", 71 }, { 72 method: "ExpireLease", 73 args: []interface{}{key("thing1")}, 74 err: corelease.ErrInvalid, 75 parallelCallback: func(mu *sync.Mutex, leases leaseMap) { 76 mu.Lock() 77 delete(leases, key("thing1")) 78 mu.Unlock() 79 80 select { 81 case slowStarted <- struct{}{}: 82 case <-time.After(coretesting.LongWait): 83 c.Errorf("timed out sending slowStarted") 84 } 85 select { 86 case <-slowFinish: 87 case <-time.After(coretesting.LongWait): 88 c.Errorf("timed out waiting for slowFinish") 89 } 90 91 }, 92 }, { 93 method: "Refresh", 94 }, { 95 method: "ExpireLease", 96 args: []interface{}{key("thing2")}, 97 callback: func(leases leaseMap) { 98 delete(leases, key("thing2")) 99 close(quickFinished) 100 }, 101 }}, 102 } 103 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 104 select { 105 case <-slowStarted: 106 case <-time.After(coretesting.LongWait): 107 c.Fatalf("timed out waiting for slowStarted") 108 } 109 // The Waiter here should be the Clock.After in tick() that is waiting 110 // for Expire to try to cleanup. But it should eventually skip even if 111 // it is blocking 112 c.Assert(clock.WaitAdvance(50*time.Millisecond, coretesting.LongWait, 1), jc.ErrorIsNil) 113 // The next waiter will be the main loop, noticing that the next time to expire is 114 // in 1s 115 c.Assert(clock.WaitAdvance(time.Second-50*time.Millisecond, coretesting.LongWait, 1), jc.ErrorIsNil) 116 117 select { 118 case <-quickFinished: 119 case <-time.After(coretesting.LongWait): 120 c.Fatalf("timed out waiting for quickFinished") 121 } 122 123 close(slowFinish) 124 125 }) 126 } 127 128 func (s *AsyncSuite) TestExpiryTimeout(c *gc.C) { 129 // When a timeout happens on expiry we retry. 130 expireCalls := make(chan struct{}) 131 fix := Fixture{ 132 leases: leaseMap{ 133 key("requiem"): { 134 Holder: "verdi", 135 Expiry: offset(-time.Second), 136 }, 137 }, 138 expectCalls: []call{{ 139 method: "Refresh", 140 }, { 141 method: "ExpireLease", 142 args: []interface{}{key("requiem")}, 143 err: corelease.ErrTimeout, 144 callback: func(_ leaseMap) { 145 select { 146 case expireCalls <- struct{}{}: 147 case <-time.After(coretesting.LongWait): 148 c.Errorf("timed out sending expired") 149 } 150 }, 151 }, { 152 method: "Refresh", 153 }, { 154 method: "ExpireLease", 155 args: []interface{}{key("requiem")}, 156 callback: func(leases leaseMap) { 157 delete(leases, key("requiem")) 158 close(expireCalls) 159 }, 160 }}, 161 } 162 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 163 select { 164 case <-expireCalls: 165 case <-time.After(coretesting.LongWait): 166 c.Fatalf("timed out waiting for 1st expireCall") 167 } 168 169 // Just the retry delay is waiting 170 c.Assert(clock.WaitAdvance(50*time.Millisecond, coretesting.LongWait, 1), jc.ErrorIsNil) 171 172 select { 173 case _, ok := <-expireCalls: 174 c.Assert(ok, gc.Equals, false) 175 case <-time.After(coretesting.LongWait): 176 c.Fatalf("timed out waiting for 2nd expireCall") 177 } 178 }) 179 } 180 181 func (s *AsyncSuite) TestExpiryRepeatedTimeout(c *gc.C) { 182 // When a timeout happens on expiry we retry - if we hit the retry 183 // limit we should kill the manager. 184 expireCalls := make(chan struct{}) 185 186 var calls []call 187 for i := 0; i < lease.MaxRetries; i++ { 188 calls = append(calls, 189 call{method: "Refresh"}, 190 call{ 191 method: "ExpireLease", 192 args: []interface{}{key("requiem")}, 193 err: corelease.ErrTimeout, 194 callback: func(_ leaseMap) { 195 select { 196 case expireCalls <- struct{}{}: 197 case <-time.After(coretesting.LongWait): 198 c.Fatalf("timed out sending expired") 199 } 200 }, 201 }, 202 ) 203 } 204 fix := Fixture{ 205 leases: leaseMap{ 206 key("requiem"): { 207 Holder: "mozart", 208 Expiry: offset(-time.Second), 209 }, 210 }, 211 expectCalls: calls, 212 expectDirty: true, 213 } 214 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 215 select { 216 case <-expireCalls: 217 case <-time.After(coretesting.LongWait): 218 c.Fatalf("timed out waiting for 1st expireCall") 219 } 220 221 delay := lease.InitialRetryDelay 222 for i := 0; i < lease.MaxRetries-1; i++ { 223 c.Logf("retry %d", i+1) 224 // One timer: 225 // - retryingExpiry timers 226 // - nextTick just fired and is waiting for expire to complete 227 // before it resets 228 err := clock.WaitAdvance(delay, coretesting.LongWait, 1) 229 c.Assert(err, jc.ErrorIsNil) 230 select { 231 case <-expireCalls: 232 case <-time.After(coretesting.LongWait): 233 c.Fatalf("timed out waiting for expireCall") 234 } 235 delay = time.Duration(float64(delay)*lease.RetryBackoffFactor + 1) 236 } 237 workertest.CheckAlive(c, manager) 238 }) 239 } 240 241 func (s *AsyncSuite) TestExpiryInterruptedRetry(c *gc.C) { 242 // Check that retries are stopped when the manager is killed. 243 expireCalls := make(chan struct{}) 244 fix := Fixture{ 245 leases: leaseMap{ 246 key("requiem"): { 247 Holder: "fauré", 248 Expiry: offset(-time.Second), 249 }, 250 }, 251 expectCalls: []call{{ 252 method: "Refresh", 253 }, { 254 method: "ExpireLease", 255 args: []interface{}{key("requiem")}, 256 err: corelease.ErrTimeout, 257 callback: func(_ leaseMap) { 258 select { 259 case expireCalls <- struct{}{}: 260 case <-time.After(coretesting.LongWait): 261 c.Errorf("timed out sending expired") 262 } 263 }, 264 }}, 265 } 266 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 267 select { 268 case <-expireCalls: 269 case <-time.After(coretesting.LongWait): 270 c.Fatalf("timed out waiting for 1st expireCall") 271 } 272 273 // The retry loop has a waiter, but the core loop's timer has just fired 274 // and because expire hasn't completed, it hasn't been reset. 275 c.Assert(clock.WaitAdvance(0, coretesting.LongWait, 1), jc.ErrorIsNil) 276 277 // Stopping the worker should cancel the retry. 278 c.Assert(worker.Stop(manager), jc.ErrorIsNil) 279 280 // Advance the clock to trigger the next expire retry 281 c.Assert(clock.WaitAdvance(50*time.Millisecond, coretesting.ShortWait, 1), jc.ErrorIsNil) 282 283 // Allow some wallclock time for a non-cancelled retry to 284 // happen if stopping the worker didn't cancel it. This is not 285 // ideal but I can't see a better way to verify that the retry 286 // doesn't happen - adding an exploding call to expectCalls 287 // makes the store wait for that call to be made. This is 288 // verified to pass reliably if the retry gets cancelled and 289 // fail reliably otherwise. 290 time.Sleep(coretesting.ShortWait) 291 }) 292 } 293 294 func (s *AsyncSuite) TestClaimSlow(c *gc.C) { 295 slowStarted := make(chan struct{}) 296 slowFinish := make(chan struct{}) 297 298 fix := Fixture{ 299 leases: leaseMap{ 300 key("dmdc"): { 301 Holder: "terry", 302 Expiry: offset(time.Second), 303 }, 304 }, 305 expectCalls: []call{{ 306 method: "ExtendLease", 307 args: []interface{}{ 308 key("dmdc"), 309 corelease.Request{"terry", time.Minute}, 310 }, 311 err: corelease.ErrInvalid, 312 parallelCallback: func(mu *sync.Mutex, leases leaseMap) { 313 select { 314 case slowStarted <- struct{}{}: 315 case <-time.After(coretesting.LongWait): 316 c.Errorf("timed out sending slowStarted") 317 } 318 select { 319 case <-slowFinish: 320 case <-time.After(coretesting.LongWait): 321 c.Errorf("timed out waiting for slowFinish") 322 } 323 mu.Lock() 324 leases[key("dmdc")] = corelease.Info{ 325 Holder: "lance", 326 Expiry: offset(time.Minute), 327 } 328 mu.Unlock() 329 }, 330 }, { 331 method: "ClaimLease", 332 args: []interface{}{ 333 key("antiquisearchers"), 334 corelease.Request{"art", time.Minute}, 335 }, 336 callback: func(leases leaseMap) { 337 leases[key("antiquisearchers")] = corelease.Info{ 338 Holder: "art", 339 Expiry: offset(time.Minute), 340 } 341 }, 342 }}, 343 } 344 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 345 claimer, err := manager.Claimer("namespace", "modelUUID") 346 c.Assert(err, jc.ErrorIsNil) 347 348 response1 := make(chan error) 349 go func() { 350 response1 <- claimer.Claim("dmdc", "terry", time.Minute) 351 }() 352 353 select { 354 case <-slowStarted: 355 case <-time.After(coretesting.LongWait): 356 c.Fatalf("timed out waiting for slowStarted") 357 } 358 response2 := make(chan error) 359 go func() { 360 response2 <- claimer.Claim("antiquisearchers", "art", time.Minute) 361 }() 362 363 // response1 should have failed its claim, and now be waiting to retry 364 // only 1 waiter, which is the 'when should we expire next' timer. 365 c.Assert(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 1), jc.ErrorIsNil) 366 367 // We should be able to get the response for the second claim 368 // even though the first hasn't come back yet. 369 select { 370 case err := <-response2: 371 c.Assert(err, jc.ErrorIsNil) 372 case <-response1: 373 c.Fatalf("response1 was ready") 374 case <-time.After(coretesting.LongWait): 375 c.Fatalf("timed out waiting for response2") 376 } 377 378 close(slowFinish) 379 380 c.Assert(clock.WaitAdvance(50*time.Millisecond, testing.LongWait, 1), jc.ErrorIsNil) 381 382 // Now response1 should come back. 383 select { 384 case err := <-response1: 385 c.Assert(errors.Cause(err), gc.Equals, corelease.ErrClaimDenied) 386 case <-time.After(coretesting.LongWait): 387 c.Fatalf("timed out waiting for response1") 388 } 389 }) 390 } 391 392 func (s *AsyncSuite) TestClaimTwoErrors(c *gc.C) { 393 oneStarted := make(chan struct{}) 394 oneFinish := make(chan struct{}) 395 twoStarted := make(chan struct{}) 396 twoFinish := make(chan struct{}) 397 398 fix := Fixture{ 399 expectDirty: true, 400 expectCalls: []call{{ 401 method: "ClaimLease", 402 args: []interface{}{ 403 key("one"), 404 corelease.Request{"terry", time.Minute}, 405 }, 406 err: errors.New("terry is bad"), 407 parallelCallback: func(mu *sync.Mutex, leases leaseMap) { 408 close(oneStarted) 409 select { 410 case <-oneFinish: 411 case <-time.After(coretesting.LongWait): 412 c.Errorf("timed out waiting for oneFinish") 413 } 414 }, 415 }, { 416 method: "ClaimLease", 417 args: []interface{}{ 418 key("two"), 419 corelease.Request{"lance", time.Minute}, 420 }, 421 err: errors.New("lance is also bad"), 422 parallelCallback: func(mu *sync.Mutex, leases leaseMap) { 423 close(twoStarted) 424 select { 425 case <-twoFinish: 426 case <-time.After(coretesting.LongWait): 427 c.Errorf("timed out waiting for twoFinish") 428 } 429 }, 430 }}, 431 } 432 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 433 claimer, err := manager.Claimer("namespace", "modelUUID") 434 c.Assert(err, jc.ErrorIsNil) 435 436 response1 := make(chan error) 437 go func() { 438 response1 <- claimer.Claim("one", "terry", time.Minute) 439 }() 440 select { 441 case <-oneStarted: 442 case <-time.After(coretesting.LongWait): 443 c.Fatalf("timed out waiting for oneStarted") 444 } 445 446 response2 := make(chan error) 447 go func() { 448 response2 <- claimer.Claim("two", "lance", time.Minute) 449 }() 450 451 select { 452 case <-twoStarted: 453 case <-time.After(coretesting.LongWait): 454 c.Fatalf("timed out waiting for twoStarted") 455 } 456 457 // By now, both of the claims have had their processing started 458 // by the store, so the lease manager will have two elements 459 // in the wait group. 460 close(oneFinish) 461 // We should be able to get error responses from both of them. 462 select { 463 case err1 := <-response1: 464 c.Check(err1, gc.ErrorMatches, "lease manager stopped") 465 case <-time.After(coretesting.LongWait): 466 c.Fatalf("timed out waiting for response2") 467 } 468 469 close(twoFinish) 470 select { 471 case err2 := <-response2: 472 c.Check(err2, gc.ErrorMatches, "lease manager stopped") 473 case <-time.After(coretesting.LongWait): 474 c.Fatalf("timed out waiting for response2") 475 } 476 477 // Since we unblock one before two, we know the error from 478 // the manager is bad terry 479 err = workertest.CheckKilled(c, manager) 480 c.Assert(err, gc.ErrorMatches, "terry is bad") 481 }) 482 } 483 484 func (s *AsyncSuite) TestClaimTimeout(c *gc.C) { 485 // When a claim times out we retry. 486 claimCalls := make(chan struct{}) 487 fix := Fixture{ 488 expectCalls: []call{{ 489 method: "ClaimLease", 490 args: []interface{}{ 491 key("icecream"), 492 corelease.Request{"rosie", time.Minute}, 493 }, 494 err: corelease.ErrTimeout, 495 callback: func(_ leaseMap) { 496 select { 497 case claimCalls <- struct{}{}: 498 case <-time.After(coretesting.LongWait): 499 c.Fatalf("timed out sending claim") 500 } 501 }, 502 }, { 503 method: "ClaimLease", 504 args: []interface{}{ 505 key("icecream"), 506 corelease.Request{"rosie", time.Minute}, 507 }, 508 callback: func(leases leaseMap) { 509 leases[key("icecream")] = corelease.Info{ 510 Holder: "rosie", 511 Expiry: offset(time.Minute), 512 } 513 }, 514 }}, 515 } 516 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 517 result := make(chan error) 518 claimer, err := manager.Claimer("namespace", "modelUUID") 519 c.Assert(err, jc.ErrorIsNil) 520 go func() { 521 result <- claimer.Claim("icecream", "rosie", time.Minute) 522 }() 523 524 select { 525 case <-claimCalls: 526 case <-time.After(coretesting.LongWait): 527 c.Fatalf("timed out waiting for claim") 528 } 529 530 // Two waiters: 531 // - one is the nextTick timer, set for 1 minute in the future 532 // - two is the claim retry timer 533 err = clock.WaitAdvance(50*time.Millisecond, coretesting.LongWait, 2) 534 535 select { 536 case err := <-result: 537 c.Assert(err, jc.ErrorIsNil) 538 case <-time.After(coretesting.LongWait): 539 c.Fatalf("timed out waiting for response") 540 } 541 }) 542 } 543 544 func (s *AsyncSuite) TestClaimNoticesEarlyExpiry(c *gc.C) { 545 fix := Fixture{ 546 leases: leaseMap{ 547 key("dmdc"): { 548 Holder: "terry", 549 Expiry: offset(10 * time.Minute), 550 }, 551 }, 552 expectCalls: []call{{ 553 method: "ClaimLease", 554 args: []interface{}{ 555 key("icecream"), 556 corelease.Request{"rosie", time.Minute}, 557 }, 558 callback: func(leases leaseMap) { 559 leases[key("icecream")] = corelease.Info{ 560 Holder: "rosie", 561 Expiry: offset(time.Minute), 562 } 563 }, 564 }, { 565 method: "ClaimLease", 566 args: []interface{}{ 567 key("fudge"), 568 corelease.Request{"chocolate", time.Minute}, 569 }, 570 callback: func(leases leaseMap) { 571 leases[key("fudge")] = corelease.Info{ 572 Holder: "chocolate", 573 Expiry: offset(2 * time.Minute), 574 } 575 }, 576 }, { 577 method: "Refresh", 578 }, { 579 method: "ExpireLease", 580 args: []interface{}{key("icecream")}, 581 callback: func(leases leaseMap) { 582 delete(leases, key("icecream")) 583 }, 584 }}, 585 } 586 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 587 // When we first start, we should not yet call Expire because the 588 // Expiry should be 10 minutes into the future. But the first claim 589 // will create an entry that expires in only 1 minute, so we should 590 // reset our expire timeout 591 claimer, err := manager.Claimer("namespace", "modelUUID") 592 c.Assert(err, jc.ErrorIsNil) 593 err = claimer.Claim("icecream", "rosie", time.Minute) 594 c.Assert(err, jc.ErrorIsNil) 595 // We sleep for 30s which *shouldn't* trigger any Expiry. And then we get 596 // another claim that also wants 1 minute duration. But that should not cause the 597 // timer to wake up in 1minute, but the 30s that are remaining. 598 c.Assert(clock.WaitAdvance(30*time.Second, testing.LongWait, 1), jc.ErrorIsNil) 599 // The second claim tries to set a timeout of another minute, but that should 600 // not cause the timer to get reset any later than it already is. 601 // Chocolate is also given a slightly longer timeout (2min after epoch) 602 err = claimer.Claim("fudge", "chocolate", time.Minute) 603 c.Assert(err, jc.ErrorIsNil) 604 // Now when we advance the clock another 30s, it should wake up and 605 // expire "icecream", and then queue up that we should expire "fudge" 606 // 1m later 607 c.Assert(clock.WaitAdvance(30*time.Second, testing.LongWait, 1), jc.ErrorIsNil) 608 }) 609 } 610 611 func (s *AsyncSuite) TestClaimRepeatedTimeout(c *gc.C) { 612 // When a claim times out too many times we give up. 613 claimCalls := make(chan struct{}) 614 var calls []call 615 for i := 0; i < lease.MaxRetries; i++ { 616 calls = append(calls, call{ 617 method: "ClaimLease", 618 args: []interface{}{ 619 key("icecream"), 620 corelease.Request{"rosie", time.Minute}, 621 }, 622 err: corelease.ErrTimeout, 623 callback: func(_ leaseMap) { 624 select { 625 case claimCalls <- struct{}{}: 626 case <-time.After(coretesting.LongWait): 627 c.Fatalf("timed out sending claim") 628 } 629 }, 630 }) 631 } 632 fix := Fixture{ 633 expectCalls: calls, 634 expectDirty: true, 635 } 636 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 637 result := make(chan error) 638 claimer, err := manager.Claimer("namespace", "modelUUID") 639 c.Assert(err, jc.ErrorIsNil) 640 go func() { 641 result <- claimer.Claim("icecream", "rosie", time.Minute) 642 }() 643 644 duration := lease.InitialRetryDelay 645 for i := 0; i < lease.MaxRetries-1; i++ { 646 c.Logf("retry %d", i) 647 select { 648 case <-claimCalls: 649 case <-result: 650 c.Fatalf("got result too soon") 651 case <-time.After(coretesting.LongWait): 652 c.Fatalf("timed out waiting for claim call") 653 } 654 655 // There should be 2 waiters: 656 // - nextTick has a timer once things expire 657 // - retryingClaim has an attempt timer 658 c.Assert(clock.WaitAdvance(duration, coretesting.LongWait, 2), jc.ErrorIsNil) 659 duration = time.Duration(float64(duration)*lease.RetryBackoffFactor + 1) 660 } 661 662 select { 663 case <-claimCalls: 664 case <-time.After(coretesting.LongWait): 665 c.Fatalf("timed out waiting for final claim call") 666 } 667 668 select { 669 case err := <-result: 670 c.Assert(errors.Cause(err), gc.Equals, corelease.ErrTimeout) 671 case <-time.After(coretesting.LongWait): 672 c.Fatalf("timed out waiting for result") 673 } 674 675 workertest.CheckAlive(c, manager) 676 }) 677 } 678 679 func (s *AsyncSuite) TestClaimRepeatedInvalid(c *gc.C) { 680 // When a claim is invalid for too long, we give up 681 claimCalls := make(chan struct{}) 682 var calls []call 683 for i := 0; i < lease.MaxRetries; i++ { 684 calls = append(calls, call{ 685 method: "ClaimLease", 686 args: []interface{}{ 687 key("icecream"), 688 corelease.Request{"rosie", time.Minute}, 689 }, 690 err: corelease.ErrInvalid, 691 callback: func(_ leaseMap) { 692 select { 693 case claimCalls <- struct{}{}: 694 case <-time.After(coretesting.LongWait): 695 c.Fatalf("timed out sending claim") 696 } 697 }, 698 }) 699 } 700 fix := Fixture{ 701 expectCalls: calls, 702 expectDirty: true, 703 } 704 fix.RunTest(c, func(manager *lease.Manager, clock *testclock.Clock) { 705 result := make(chan error) 706 claimer, err := manager.Claimer("namespace", "modelUUID") 707 c.Assert(err, jc.ErrorIsNil) 708 go func() { 709 result <- claimer.Claim("icecream", "rosie", time.Minute) 710 }() 711 712 duration := lease.InitialRetryDelay 713 for i := 0; i < lease.MaxRetries-1; i++ { 714 c.Logf("retry %d", i) 715 select { 716 case <-claimCalls: 717 case <-result: 718 c.Fatalf("got result too soon") 719 case <-time.After(coretesting.LongWait): 720 c.Fatalf("timed out waiting for claim call") 721 } 722 723 // There should be 2 waiters: 724 // - nextTick has a timer once things expire 725 // - retryingClaim has an attempt timer 726 c.Assert(clock.WaitAdvance(duration, coretesting.LongWait, 2), jc.ErrorIsNil) 727 duration = time.Duration(float64(duration)*lease.RetryBackoffFactor + 1) 728 } 729 730 select { 731 case <-claimCalls: 732 case <-time.After(coretesting.LongWait): 733 c.Fatalf("timed out waiting for final claim call") 734 } 735 736 select { 737 case err := <-result: 738 c.Assert(errors.Cause(err), gc.Equals, corelease.ErrClaimDenied) 739 case <-time.After(coretesting.LongWait): 740 c.Fatalf("timed out waiting for result") 741 } 742 743 workertest.CheckAlive(c, manager) 744 }) 745 } 746 747 func (s *AsyncSuite) TestWaitsForGoroutines(c *gc.C) { 748 // The manager should wait for all of its child expire and claim 749 // goroutines to be finished before it stops. 750 tickStarted := make(chan struct{}) 751 tickFinish := make(chan struct{}) 752 claimStarted := make(chan struct{}) 753 claimFinish := make(chan struct{}) 754 fix := Fixture{ 755 leases: leaseMap{ 756 key("legacy"): { 757 Holder: "culprate", 758 Expiry: offset(-time.Second), 759 }, 760 }, 761 expectCalls: []call{{ 762 method: "Refresh", 763 }, { 764 method: "ExpireLease", 765 args: []interface{}{key("legacy")}, 766 parallelCallback: func(_ *sync.Mutex, _ leaseMap) { 767 close(tickStarted) 768 // Block until asked to stop. 769 <-tickFinish 770 }, 771 }, { 772 method: "ClaimLease", 773 args: []interface{}{ 774 key("blooadoath"), 775 corelease.Request{"hand", time.Minute}, 776 }, 777 parallelCallback: func(_ *sync.Mutex, _ leaseMap) { 778 close(claimStarted) 779 <-claimFinish 780 }, 781 }}, 782 } 783 fix.RunTest(c, func(manager *lease.Manager, _ *testclock.Clock) { 784 select { 785 case <-tickStarted: 786 case <-time.After(coretesting.LongWait): 787 c.Fatalf("timed out waiting for expire start") 788 } 789 790 result := make(chan error) 791 claimer, err := manager.Claimer("namespace", "modelUUID") 792 c.Assert(err, jc.ErrorIsNil) 793 go func() { 794 result <- claimer.Claim("blooadoath", "hand", time.Minute) 795 }() 796 797 // Ensure we've called claim in the store and are waiting for 798 // a response. 799 select { 800 case <-claimStarted: 801 case <-time.After(coretesting.LongWait): 802 c.Fatalf("timed out waiting for claim start") 803 } 804 805 // If we kill the manager now it won't finish until the claim 806 // call finishes (no worries about timeouts because we aren't 807 // advancing the test clock). 808 manager.Kill() 809 workertest.CheckAlive(c, manager) 810 811 // Now if we finish the claim, the result comes back. 812 close(claimFinish) 813 814 select { 815 case err := <-result: 816 c.Assert(err, gc.ErrorMatches, "lease manager stopped") 817 case <-time.After(coretesting.LongWait): 818 c.Fatalf("timed out waiting for result") 819 } 820 821 workertest.CheckAlive(c, manager) 822 823 // And when we finish the expire the worker stops. 824 close(tickFinish) 825 826 err = workertest.CheckKilled(c, manager) 827 c.Assert(err, jc.ErrorIsNil) 828 }) 829 }