package routetable import ( "net/netip" "runtime" "testing" "time" ) // TestPeersDownLetsReadsIn reads the table's counts again and again while a // peer that carries more than two batches of prefixes goes down. PeersDown lets // the lock go after every walkBatch prefixes, so a read must get in while the // peer still carries some of its prefixes but no longer all. func TestPeersDownLetsReadsIn(t *testing.T) { t.Parallel() const ( asn = 1000 peer = "192.0.2.1" // prefixCount is more prefixes than two of walkBatch. prefixCount = 2*walkBatch + 1 ) routeTime := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) table := New(Params{}) addr := netip.MustParseAddr("1.0.0.0") for range prefixCount { prefix := netip.PrefixFrom(addr, addr.BitLen()) table.Announce(prefix, asn, Route{Peer: peer, Time: routeTime}) addr = addr.Next() } // Hold a read lock until PeersDown waits for the lock, so that the first // read below starts once PeersDown holds it. TryRLock fails once a writer // waits. table.mu.RLock() peerDownDone := make(chan struct{}) go func() { defer close(peerDownDone) table.PeersDown([]string{peer}) }() for table.mu.TryRLock() { table.mu.RUnlock() runtime.Gosched() } table.mu.RUnlock() for { routes := table.Counts().IPv4Routes if routes > 0 && routes < prefixCount { break } select { case <-peerDownDone: t.Fatal("no read got in while PeersDown ran") default: } } <-peerDownDone }