github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/presence/presence_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package presence_test 5 6 import ( 7 "fmt" 8 "strconv" 9 "sync" 10 "sync/atomic" 11 stdtesting "testing" 12 "time" 13 14 "github.com/juju/errors" 15 gitjujutesting "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/juju/worker.v1" 21 "gopkg.in/mgo.v2" 22 "gopkg.in/mgo.v2/bson" 23 "gopkg.in/tomb.v2" 24 25 "github.com/juju/juju/state/presence" 26 "github.com/juju/juju/testing" 27 ) 28 29 func TestPackage(t *stdtesting.T) { 30 testing.MgoTestPackage(t) 31 } 32 33 type PresenceSuite struct { 34 gitjujutesting.MgoSuite 35 testing.BaseSuite 36 presence *mgo.Collection 37 pings *mgo.Collection 38 modelTag names.ModelTag 39 } 40 41 var _ = gc.Suite(&PresenceSuite{}) 42 43 func (s *PresenceSuite) SetUpSuite(c *gc.C) { 44 s.BaseSuite.SetUpSuite(c) 45 s.MgoSuite.SetUpSuite(c) 46 uuid, err := utils.NewUUID() 47 c.Assert(err, jc.ErrorIsNil) 48 s.modelTag = names.NewModelTag(uuid.String()) 49 } 50 51 func (s *PresenceSuite) TearDownSuite(c *gc.C) { 52 s.MgoSuite.TearDownSuite(c) 53 s.BaseSuite.TearDownSuite(c) 54 } 55 56 func (s *PresenceSuite) SetUpTest(c *gc.C) { 57 s.BaseSuite.SetUpTest(c) 58 s.MgoSuite.SetUpTest(c) 59 60 db := s.MgoSuite.Session.DB("presence") 61 s.presence = db.C("presence") 62 s.pings = db.C("presence.pings") 63 64 presence.FakeTimeSlot(0) 65 } 66 67 func (s *PresenceSuite) TearDownTest(c *gc.C) { 68 s.MgoSuite.TearDownTest(c) 69 s.BaseSuite.TearDownTest(c) 70 71 presence.RealTimeSlot() 72 presence.RealPeriod() 73 } 74 75 func assertChange(c *gc.C, watch <-chan presence.Change, want presence.Change) { 76 select { 77 case got := <-watch: 78 if got != want { 79 c.Fatalf("watch reported %v, want %v", got, want) 80 } 81 case <-time.After(testing.LongWait): 82 c.Fatalf("watch reported nothing, want %v", want) 83 } 84 } 85 86 func assertNoChange(c *gc.C, watch <-chan presence.Change) { 87 select { 88 case got := <-watch: 89 c.Fatalf("watch reported %v, want nothing", got) 90 case <-time.After(testing.ShortWait): 91 } 92 } 93 94 func assertAlive(c *gc.C, w *presence.Watcher, key string, expAlive bool) { 95 realAlive, err := w.Alive(key) 96 c.Assert(err, jc.ErrorIsNil) 97 c.Assert(realAlive, gc.Equals, expAlive) 98 } 99 100 // assertStopped stops a worker and waits until it reports stopped. 101 // Use this method in favor of defer w.Stop() because you _must_ ensure 102 // that the worker has stopped, and thus is no longer using its mgo 103 // session before TearDownTest shuts down the connection. 104 func assertStopped(c *gc.C, w worker.Worker) { 105 c.Assert(worker.Stop(w), jc.ErrorIsNil) 106 } 107 108 func (s *PresenceSuite) TestErrAndDead(c *gc.C) { 109 w := presence.NewWatcher(s.presence, s.modelTag) 110 defer assertStopped(c, w) 111 112 c.Assert(errors.Cause(w.Err()), gc.Equals, tomb.ErrStillAlive) 113 select { 114 case <-w.Dead(): 115 c.Fatalf("Dead channel fired unexpectedly") 116 default: 117 } 118 c.Assert(w.Stop(), gc.IsNil) 119 c.Assert(w.Err(), gc.IsNil) 120 select { 121 case <-w.Dead(): 122 default: 123 c.Fatalf("Dead channel should have fired") 124 } 125 } 126 127 func (s *PresenceSuite) getDirectRecorder() presence.PingRecorder { 128 return presence.DirectRecordFunc(s.presence) 129 } 130 131 func (s *PresenceSuite) TestAliveError(c *gc.C) { 132 w := presence.NewWatcher(s.presence, s.modelTag) 133 c.Assert(w.Stop(), gc.IsNil) 134 135 alive, err := w.Alive("a") 136 c.Assert(err, gc.ErrorMatches, ".*: watcher is dying") 137 c.Assert(alive, jc.IsFalse) 138 w.Wait() 139 } 140 141 func (s *PresenceSuite) TestWorkflow(c *gc.C) { 142 w := presence.NewWatcher(s.presence, s.modelTag) 143 pa := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 144 pb := presence.NewPinger(s.presence, s.modelTag, "b", s.getDirectRecorder) 145 defer assertStopped(c, w) 146 defer assertStopped(c, pa) 147 defer assertStopped(c, pb) 148 149 assertAlive(c, w, "a", false) 150 assertAlive(c, w, "b", false) 151 152 // Buffer one entry to avoid blocking the watcher here. 153 cha := make(chan presence.Change, 1) 154 chb := make(chan presence.Change, 1) 155 w.Watch("a", cha) 156 w.Watch("b", chb) 157 158 // Initial events with current status. 159 assertChange(c, cha, presence.Change{"a", false}) 160 assertChange(c, chb, presence.Change{"b", false}) 161 162 w.StartSync() 163 assertNoChange(c, cha) 164 assertNoChange(c, chb) 165 166 c.Assert(pa.Start(), gc.IsNil) 167 168 w.StartSync() 169 assertChange(c, cha, presence.Change{"a", true}) 170 assertNoChange(c, cha) 171 assertNoChange(c, chb) 172 173 assertAlive(c, w, "a", true) 174 assertAlive(c, w, "b", false) 175 176 // Changes while the channel is out are not observed. 177 w.Unwatch("a", cha) 178 assertNoChange(c, cha) 179 pa.KillForTesting() 180 w.Sync() 181 pa = presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 182 pa.Start() 183 w.StartSync() 184 assertNoChange(c, cha) 185 186 // We can still query it manually, though. 187 assertAlive(c, w, "a", true) 188 assertAlive(c, w, "b", false) 189 190 // Initial positive event. No refresh needed. 191 w.Watch("a", cha) 192 assertChange(c, cha, presence.Change{"a", true}) 193 194 c.Assert(pb.Start(), gc.IsNil) 195 196 w.StartSync() 197 assertChange(c, chb, presence.Change{"b", true}) 198 assertNoChange(c, cha) 199 assertNoChange(c, chb) 200 201 c.Assert(pa.Stop(), gc.IsNil) 202 203 w.StartSync() 204 assertNoChange(c, cha) 205 assertNoChange(c, chb) 206 207 // pb is running, pa isn't. 208 c.Assert(pa.KillForTesting(), gc.IsNil) 209 c.Assert(pb.KillForTesting(), gc.IsNil) 210 211 w.StartSync() 212 assertChange(c, cha, presence.Change{"a", false}) 213 assertChange(c, chb, presence.Change{"b", false}) 214 215 assertStopped(c, w) 216 } 217 218 func (s *PresenceSuite) TestScale(c *gc.C) { 219 const N = 1000 220 var ps []*presence.Pinger 221 defer func() { 222 for _, p := range ps { 223 p.Stop() 224 } 225 }() 226 227 c.Logf("Starting %d pingers...", N) 228 for i := 0; i < N; i++ { 229 p := presence.NewPinger(s.presence, s.modelTag, strconv.Itoa(i), s.getDirectRecorder) 230 c.Assert(p.Start(), gc.IsNil) 231 ps = append(ps, p) 232 } 233 234 c.Logf("Killing odd ones...") 235 for i := 1; i < N; i += 2 { 236 c.Assert(ps[i].KillForTesting(), gc.IsNil) 237 } 238 239 c.Logf("Checking who's still alive...") 240 w := presence.NewWatcher(s.presence, s.modelTag) 241 defer assertStopped(c, w) 242 w.Sync() 243 ch := make(chan presence.Change) 244 for i := 0; i < N; i++ { 245 k := strconv.Itoa(i) 246 w.Watch(k, ch) 247 if i%2 == 0 { 248 assertChange(c, ch, presence.Change{k, true}) 249 } else { 250 assertChange(c, ch, presence.Change{k, false}) 251 } 252 } 253 } 254 255 func (s *PresenceSuite) TestExpiry(c *gc.C) { 256 w := presence.NewWatcher(s.presence, s.modelTag) 257 p := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 258 defer assertStopped(c, w) 259 defer assertStopped(c, p) 260 261 ch := make(chan presence.Change) 262 w.Watch("a", ch) 263 assertChange(c, ch, presence.Change{"a", false}) 264 265 c.Assert(p.Start(), gc.IsNil) 266 w.StartSync() 267 assertChange(c, ch, presence.Change{"a", true}) 268 269 // Still alive in previous slot. 270 presence.FakeTimeSlot(1) 271 w.StartSync() 272 assertNoChange(c, ch) 273 274 // Two last slots are empty. 275 presence.FakeTimeSlot(2) 276 w.StartSync() 277 assertChange(c, ch, presence.Change{"a", false}) 278 279 // Already dead so killing isn't noticed. 280 p.KillForTesting() 281 w.StartSync() 282 assertNoChange(c, ch) 283 } 284 285 func (s *PresenceSuite) TestWatchPeriod(c *gc.C) { 286 presence.FakePeriod(1) 287 presence.RealTimeSlot() 288 289 w := presence.NewWatcher(s.presence, s.modelTag) 290 p := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 291 defer assertStopped(c, w) 292 defer assertStopped(c, p) 293 294 ch := make(chan presence.Change) 295 w.Watch("a", ch) 296 assertChange(c, ch, presence.Change{"a", false}) 297 298 // A single ping. 299 c.Assert(p.Start(), gc.IsNil) 300 c.Assert(p.Stop(), gc.IsNil) 301 302 // Wait for next periodic refresh. 303 time.Sleep(1 * time.Second) 304 assertChange(c, ch, presence.Change{"a", true}) 305 } 306 307 func (s *PresenceSuite) TestWatchUnwatchOnQueue(c *gc.C) { 308 w := presence.NewWatcher(s.presence, s.modelTag) 309 defer assertStopped(c, w) 310 ch := make(chan presence.Change, 100) 311 for i := 0; i < 100; i++ { 312 key := strconv.Itoa(i) 313 c.Logf("Adding %q", key) 314 w.Watch(key, ch) 315 } 316 for i := 1; i < 100; i += 2 { 317 key := strconv.Itoa(i) 318 c.Logf("Removing %q", key) 319 w.Unwatch(key, ch) 320 } 321 alive := make(map[string]bool) 322 for i := 0; i < 50; i++ { 323 change := <-ch 324 c.Logf("Got change for %q: %v", change.Key, change.Alive) 325 alive[change.Key] = change.Alive 326 } 327 for i := 0; i < 100; i += 2 { 328 key := strconv.Itoa(i) 329 c.Logf("Checking %q...", key) 330 c.Assert(alive[key], jc.IsFalse) 331 } 332 } 333 334 func (s *PresenceSuite) TestRestartWithoutGaps(c *gc.C) { 335 p := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 336 c.Assert(p.Start(), gc.IsNil) 337 defer assertStopped(c, p) 338 339 done := make(chan bool) 340 go func() { 341 stop := false 342 for !stop { 343 if !c.Check(p.Stop(), gc.IsNil) { 344 break 345 } 346 if !c.Check(p.Start(), gc.IsNil) { 347 break 348 } 349 select { 350 case stop = <-done: 351 default: 352 } 353 } 354 }() 355 go func() { 356 stop := false 357 for !stop { 358 w := presence.NewWatcher(s.presence, s.modelTag) 359 w.Sync() 360 alive, err := w.Alive("a") 361 assertStopped(c, w) 362 if !c.Check(err, jc.ErrorIsNil) || !c.Check(alive, jc.IsTrue) { 363 break 364 } 365 select { 366 case stop = <-done: 367 default: 368 } 369 } 370 }() 371 // TODO(jam): This forceful delay of 500ms sounds like a bad test, 372 // since we always sleep for the full timeout 373 time.Sleep(500 * time.Millisecond) 374 done <- true 375 done <- true 376 } 377 378 func (s *PresenceSuite) TestPingerPeriodAndResilience(c *gc.C) { 379 // This test verifies both the periodic pinging, 380 // and also a great property of the design: deaths 381 // also expire, which means erroneous scenarios are 382 // automatically recovered from. 383 384 const period = 1 385 presence.FakePeriod(period) 386 presence.RealTimeSlot() 387 388 w := presence.NewWatcher(s.presence, s.modelTag) 389 p1 := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 390 p2 := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 391 defer assertStopped(c, w) 392 defer assertStopped(c, p1) 393 defer assertStopped(c, p2) 394 395 // Start p1 and let it go on. 396 c.Assert(p1.Start(), gc.IsNil) 397 398 w.Sync() 399 assertAlive(c, w, "a", true) 400 401 // Start and kill p2, which will temporarily 402 // invalidate p1 and set the key as dead. 403 c.Assert(p2.Start(), gc.IsNil) 404 c.Assert(p2.KillForTesting(), gc.IsNil) 405 406 w.Sync() 407 assertAlive(c, w, "a", false) 408 409 // Wait for two periods, and check again. Since 410 // p1 is still alive, p2's death will expire and 411 // the key will come back. 412 time.Sleep(period * 2 * time.Second) 413 414 w.Sync() 415 assertAlive(c, w, "a", true) 416 } 417 418 func (s *PresenceSuite) TestStartSync(c *gc.C) { 419 w := presence.NewWatcher(s.presence, s.modelTag) 420 p := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 421 defer assertStopped(c, w) 422 defer assertStopped(c, p) 423 424 ch := make(chan presence.Change) 425 w.Watch("a", ch) 426 assertChange(c, ch, presence.Change{"a", false}) 427 428 c.Assert(p.Start(), gc.IsNil) 429 430 done := make(chan bool) 431 go func() { 432 w.StartSync() 433 w.StartSync() 434 w.StartSync() 435 done <- true 436 }() 437 438 select { 439 case <-done: 440 case <-time.After(testing.LongWait): 441 c.Fatalf("StartSync failed to return") 442 } 443 444 assertChange(c, ch, presence.Change{"a", true}) 445 } 446 447 func (s *PresenceSuite) TestSync(c *gc.C) { 448 w := presence.NewWatcher(s.presence, s.modelTag) 449 p := presence.NewPinger(s.presence, s.modelTag, "a", s.getDirectRecorder) 450 defer assertStopped(c, w) 451 defer assertStopped(c, p) 452 453 ch := make(chan presence.Change) 454 w.Watch("a", ch) 455 assertChange(c, ch, presence.Change{"a", false}) 456 457 // Nothing to do here. 458 w.Sync() 459 460 c.Assert(p.Start(), gc.IsNil) 461 462 done := make(chan bool) 463 go func() { 464 w.Sync() 465 done <- true 466 }() 467 468 select { 469 case <-done: 470 c.Fatalf("Sync returned too early") 471 // Note(jam): This used to wait 200ms to ensure that 472 // Sync was actually blocked waiting for a presence 473 // change. Is ShortWait long enough for this assurance? 474 case <-time.After(testing.ShortWait): 475 } 476 477 assertChange(c, ch, presence.Change{"a", true}) 478 479 select { 480 case <-done: 481 case <-time.After(testing.LongWait): 482 c.Fatalf("Sync failed to returned") 483 } 484 } 485 486 func (s *PresenceSuite) TestTwoModels(c *gc.C) { 487 key := "a" 488 w1, p1, ch1 := s.setup(c, key) 489 defer assertStopped(c, w1) 490 defer assertStopped(c, p1) 491 492 w2, p2, ch2 := s.setup(c, key) 493 defer assertStopped(c, w2) 494 defer assertStopped(c, p2) 495 496 c.Assert(p1.Start(), gc.IsNil) 497 w1.StartSync() 498 w2.StartSync() 499 assertNoChange(c, ch2) 500 assertChange(c, ch1, presence.Change{"a", true}) 501 502 c.Assert(p2.Start(), gc.IsNil) 503 w1.StartSync() 504 w2.StartSync() 505 assertNoChange(c, ch1) 506 assertChange(c, ch2, presence.Change{"a", true}) 507 508 err := p1.KillForTesting() 509 c.Assert(err, jc.ErrorIsNil) 510 presence.FakeTimeSlot(1) 511 w1.StartSync() 512 w2.StartSync() 513 assertChange(c, ch1, presence.Change{"a", false}) 514 assertNoChange(c, ch2) 515 516 err = p2.KillForTesting() 517 c.Assert(err, jc.ErrorIsNil) 518 presence.FakeTimeSlot(2) 519 w1.StartSync() 520 w2.StartSync() 521 assertChange(c, ch2, presence.Change{"a", false}) 522 assertNoChange(c, ch1) 523 } 524 525 func newModelTag(c *gc.C) names.ModelTag { 526 uuid, err := utils.NewUUID() 527 c.Assert(err, jc.ErrorIsNil) 528 modelUUID := uuid.String() 529 return names.NewModelTag(modelUUID) 530 } 531 532 func (s *PresenceSuite) setup(c *gc.C, key string) (*presence.Watcher, *presence.Pinger, <-chan presence.Change) { 533 modelTag := newModelTag(c) 534 535 w := presence.NewWatcher(s.presence, modelTag) 536 p := presence.NewPinger(s.presence, modelTag, key, s.getDirectRecorder) 537 538 ch := make(chan presence.Change) 539 w.Watch(key, ch) 540 assertChange(c, ch, presence.Change{key, false}) 541 return w, p, ch 542 } 543 544 func countModelIds(c *gc.C, coll *mgo.Collection, modelTag names.ModelTag) int { 545 count, err := coll.Find(bson.M{"_id": bson.RegEx{"^" + modelTag.Id() + ":", ""}}).Count() 546 // either the error is NotFound or nil 547 if err != nil { 548 c.Assert(err, gc.Equals, mgo.ErrNotFound) 549 } 550 return count 551 } 552 553 func (s *PresenceSuite) TestRemovePresenceForModel(c *gc.C) { 554 key := "a" 555 556 // Start a pinger in this model 557 w1 := presence.NewWatcher(s.presence, s.modelTag) 558 p1 := presence.NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 559 ch1 := make(chan presence.Change) 560 w1.Watch(key, ch1) 561 assertChange(c, ch1, presence.Change{key, false}) 562 defer assertStopped(c, w1) 563 defer assertStopped(c, p1) 564 p1.Start() 565 w1.StartSync() 566 assertChange(c, ch1, presence.Change{"a", true}) 567 568 // Start a second model and pinger with the same key 569 modelTag2 := newModelTag(c) 570 w2 := presence.NewWatcher(s.presence, modelTag2) 571 p2 := presence.NewPinger(s.presence, modelTag2, key, s.getDirectRecorder) 572 ch2 := make(chan presence.Change) 573 w2.Watch(key, ch2) 574 assertChange(c, ch2, presence.Change{key, false}) 575 defer assertStopped(c, w2) 576 defer assertStopped(c, p2) 577 // Start them, and check that we see they're alive 578 p2.Start() 579 w2.StartSync() 580 assertChange(c, ch2, presence.Change{"a", true}) 581 582 beings := s.presence.Database.C(s.presence.Name + ".beings") 583 pings := s.presence.Database.C(s.presence.Name + ".pings") 584 seqs := s.presence.Database.C(s.presence.Name + ".seqs") 585 // we should have a being and pings for both pingers 586 c.Check(countModelIds(c, beings, s.modelTag), gc.Equals, 1) 587 c.Check(countModelIds(c, beings, modelTag2), gc.Equals, 1) 588 c.Check(countModelIds(c, pings, s.modelTag), jc.GreaterThan, 0) 589 c.Check(countModelIds(c, pings, modelTag2), jc.GreaterThan, 0) 590 c.Check(countModelIds(c, seqs, s.modelTag), gc.Equals, 1) 591 c.Check(countModelIds(c, seqs, modelTag2), gc.Equals, 1) 592 593 // kill everything in the first model 594 assertStopped(c, w1) 595 assertStopped(c, p1) 596 // And cleanup the resources 597 err := presence.RemovePresenceForModel(s.presence, s.modelTag) 598 c.Assert(err, jc.ErrorIsNil) 599 600 // Should not cause the second pinger to go dead 601 w2.StartSync() 602 assertNoChange(c, ch2) 603 604 // And we should only have the second model in the databases 605 c.Check(countModelIds(c, beings, s.modelTag), gc.Equals, 0) 606 c.Check(countModelIds(c, beings, modelTag2), gc.Equals, 1) 607 c.Check(countModelIds(c, pings, s.modelTag), gc.Equals, 0) 608 c.Check(countModelIds(c, pings, modelTag2), jc.GreaterThan, 0) 609 c.Check(countModelIds(c, seqs, s.modelTag), gc.Equals, 0) 610 c.Check(countModelIds(c, seqs, modelTag2), gc.Equals, 1) 611 612 // Removing a Model that is no longer there should not be an error 613 err = presence.RemovePresenceForModel(s.presence, s.modelTag) 614 c.Assert(err, jc.ErrorIsNil) 615 } 616 617 func (s *PresenceSuite) TestMultiplePingersForEntity(c *gc.C) { 618 // We should be able to track multiple sequences for a given Entity, without having to reread the database all the time 619 key := "a" 620 621 // Start a pinger in this model 622 w := presence.NewWatcher(s.presence, s.modelTag) 623 defer assertStopped(c, w) 624 p1 := presence.NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 625 p1.Start() 626 assertStopped(c, p1) 627 p2 := presence.NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 628 p2.Start() 629 assertStopped(c, p2) 630 p3 := presence.NewPinger(s.presence, s.modelTag, key, s.getDirectRecorder) 631 p3.Start() 632 assertStopped(c, p3) 633 w.Sync() 634 loads := w.BeingLoads() 635 c.Check(loads, jc.GreaterThan, uint64(0)) 636 alive, err := w.Alive(key) 637 c.Assert(err, jc.ErrorIsNil) 638 c.Check(alive, jc.IsTrue) 639 // When we sync a second time, all of the above entities should already be cached, so we don't have to load them again 640 w.Sync() 641 c.Check(w.BeingLoads(), gc.Equals, loads) 642 } 643 644 func (s *PresenceSuite) TestRobustness(c *gc.C) { 645 // There used to be a potential condition, where during a flush() we wait for a channel send, and while we're 646 // waiting for it, we would handle events, which might cause us to grow our pending array, which would realloc 647 // the slice. If while that happened the original watch was unwatched, then we nil the channel, but the object 648 // we were hung pending on part of the reallocated slice. 649 w := presence.NewWatcher(s.presence, s.modelTag) 650 defer assertStopped(c, w) 651 // Start a watch for changes to 'key'. Never listen for actual events on that channel, though, so we know flush() 652 // will always be blocked, but allowing other events while waiting to send that event. 653 rootKey := "key" 654 keyChan := make(chan presence.Change, 0) 655 w.Watch(rootKey, keyChan) 656 // Whenever we successfully watch in the main loop(), it starts a flush. We should now be able to build up more 657 // watches while waiting. Create enough of these that we know the slice gets reallocated 658 var wg sync.WaitGroup 659 defer wg.Wait() 660 var observed uint32 661 const numKeys = 10 662 for i := 0; i < numKeys; i++ { 663 k := fmt.Sprintf("k%d", i) 664 kChan := make(chan presence.Change, 0) 665 w.Watch("key", kChan) 666 wg.Add(1) 667 go func() { 668 defer wg.Done() 669 select { 670 case <-kChan: 671 atomic.AddUint32(&observed, 1) 672 return 673 case <-time.After(testing.LongWait): 674 c.Fatalf("timed out waiting %s for %q to see its event", testing.LongWait, k) 675 } 676 }() 677 } 678 // None of them should actually have triggered, since the very first pending object has not been listened to 679 // And now we unwatch that object 680 time.Sleep(testing.ShortWait) 681 c.Check(atomic.LoadUint32(&observed), gc.Equals, uint32(0)) 682 w.Unwatch(rootKey, keyChan) 683 // This should unblock all of them, and everything should go to observed 684 failTime := time.After(testing.LongWait) 685 o := atomic.LoadUint32(&observed) 686 for o != numKeys { 687 select { 688 case <-time.After(time.Millisecond): 689 o = atomic.LoadUint32(&observed) 690 case <-failTime: 691 c.Fatalf("only observed %d changes (expected %d) after %s time", atomic.LoadUint32(&observed), numKeys, testing.LongWait) 692 } 693 } 694 c.Check(atomic.LoadUint32(&observed), gc.Equals, uint32(numKeys)) 695 }