package inventory import ( "errors" "strings" "testing" "time" ) // Composing and sending one node's declaration is serialised: a second holder waits for the first. func TestHoldingANodeMakesTheNextHolderWait(t *testing.T) { inv := fresh(t) ctx := t.Context() release, err := inv.HoldNodes(ctx, []string{"anchor", "laptop"}) if err != nil { t.Fatal(err) } got := make(chan func(), 1) go func() { second, err := inv.HoldNodes(ctx, []string{"laptop"}) if err != nil { t.Error(err) got <- func() {} return } got <- second }() select { case <-got: t.Fatal("a node held by one caller was held by another at the same time") case <-time.After(300 * time.Millisecond): } // Another node is not held up. other, err := inv.HoldNodes(ctx, []string{"joiner"}) if err != nil { t.Fatal(err) } other() release() select { case second := <-got: second() case <-time.After(5 * time.Second): t.Fatal("releasing the node did not let the next holder in") } } // A waiter pins no pool connection while it waits, and gives up after a bounded wait saying which // node is busy. func TestAWaiterPinsNoConnectionAndGivesUp(t *testing.T) { inv := fresh(t) ctx := t.Context() release, err := inv.HoldNodes(ctx, []string{"anchor"}) if err != nil { t.Fatal(err) } defer release() savedWait, savedPoll := HoldWaitFor, HoldPoll HoldWaitFor, HoldPoll = 1500*time.Millisecond, 50*time.Millisecond defer func() { HoldWaitFor, HoldPoll = savedWait, savedPoll }() pool := inv.store.Pool() base := pool.Stat().AcquiredConns() const waiters = 3 done := make(chan error, waiters) for range waiters { go func() { _, err := inv.HoldNodes(ctx, []string{"anchor"}) done <- err }() } // While they wait, the pool lends nothing to them for longer than a look. pinned := 0 for range 10 { time.Sleep(60 * time.Millisecond) if n := int(pool.Stat().AcquiredConns() - base); n > pinned { pinned = n } } if pinned >= waiters { t.Fatalf("%d connections were held by %d waiters", pinned, waiters) } for range waiters { if err := <-done; !errors.Is(err, ErrNodeBusy) || !strings.Contains(err.Error(), "anchor") { t.Fatalf("a waiter did not give up naming the busy node: %v", err) } } }