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  }