check / check (push) Canceled after 0s
The home page's cards are worked out once a minute by Overview, which walks every prefix of the routing table under the table's read lock. After each batch of 10,000 prefixes, the walk let the lock go and took it back at once. The prefix handler, waiting for the write lock, then got in only once per batch. During a walk, its queue could fill, and the streamer then dropped updates for it. The walk now lets the lock go for as long as it held the lock for the batch, as PeerDown does. A test applies another peer's 1,000 announcements while Overview walks a table with more prefixes than five of the walk's batches. Model: opus-5-5
192 lines
4.7 KiB
Go
192 lines
4.7 KiB
Go
package routetable
|
|
|
|
import (
|
|
"net/netip"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestWalkPrefixesVisitsEveryPrefix walks a table with more prefixes than two
|
|
// of the walk's batches, while another goroutine keeps adding and removing
|
|
// other prefixes of the same mask length. The walk lets the lock go between
|
|
// batches, so those updates get in while it runs. Every prefix that was in
|
|
// the table all along must be visited once.
|
|
func TestWalkPrefixesVisitsEveryPrefix(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
asn = 1000
|
|
peer = "192.0.2.1"
|
|
// prefixCount is more prefixes than two of the walk's batches.
|
|
prefixCount = 2*walkBatch + 1
|
|
)
|
|
|
|
routeTime := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
table := New(Params{})
|
|
prefixes := make([]netip.Prefix, 0, prefixCount)
|
|
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})
|
|
prefixes = append(prefixes, prefix)
|
|
addr = addr.Next()
|
|
}
|
|
|
|
stop := make(chan struct{})
|
|
updated := make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(updated)
|
|
|
|
other := netip.MustParseAddr("2.0.0.0")
|
|
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
default:
|
|
}
|
|
|
|
prefix := netip.PrefixFrom(other, other.BitLen())
|
|
table.Announce(prefix, asn, Route{Peer: peer, Time: routeTime})
|
|
table.Withdraw(prefix, peer)
|
|
|
|
other = other.Next()
|
|
}
|
|
}()
|
|
|
|
visits := make(map[netip.Prefix]int)
|
|
|
|
table.walkPrefixes(func(prefix netip.Prefix, _ []*origin) {
|
|
visits[prefix]++
|
|
})
|
|
|
|
close(stop)
|
|
<-updated
|
|
|
|
for _, prefix := range prefixes {
|
|
if visits[prefix] != 1 {
|
|
t.Fatalf("the walk visited %s %d times, want once", prefix,
|
|
visits[prefix])
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestWalkPrefixesLetsUpdatesIn starts an update during the walk's first
|
|
// batch. The update must have been made by the time the walk visits the first
|
|
// prefix of its second batch, because the walk lets the lock go in between.
|
|
func TestWalkPrefixesLetsUpdatesIn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
asn = 1000
|
|
peer = "192.0.2.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 walkBatch + 1 {
|
|
prefix := netip.PrefixFrom(addr, addr.BitLen())
|
|
table.Announce(prefix, asn, Route{Peer: peer, Time: routeTime})
|
|
|
|
addr = addr.Next()
|
|
}
|
|
|
|
updatePrefix := netip.MustParsePrefix("2.0.0.0/8")
|
|
updateDone := make(chan struct{})
|
|
visited := 0
|
|
madeBeforeSecondBatch := false
|
|
|
|
table.walkPrefixes(func(netip.Prefix, []*origin) {
|
|
visited++
|
|
|
|
switch visited {
|
|
case 1:
|
|
go func() {
|
|
defer close(updateDone)
|
|
|
|
table.Announce(updatePrefix, asn, Route{Peer: peer, Time: routeTime})
|
|
}()
|
|
|
|
// Wait until the update waits for the lock. TryRLock fails
|
|
// once a writer waits.
|
|
for table.mu.TryRLock() {
|
|
table.mu.RUnlock()
|
|
runtime.Gosched()
|
|
}
|
|
case walkBatch + 1:
|
|
// The walk holds the read lock, so the table can be read.
|
|
madeBeforeSecondBatch = table.origins(updatePrefix) != nil
|
|
}
|
|
})
|
|
|
|
<-updateDone
|
|
|
|
if !madeBeforeSecondBatch {
|
|
t.Fatal("an update started during the walk's first batch was not made " +
|
|
"before its second batch")
|
|
}
|
|
}
|
|
|
|
// TestOverviewLetsUpdatesIn applies another peer's 1,000 announcements while
|
|
// Overview walks a table with more prefixes than five of the walk's batches.
|
|
// After each batch, the walk lets the lock go for as long as it held the lock.
|
|
// So all 1,000 announcements must be applied before Overview returns. A walk
|
|
// that takes the lock back at once lets in about one update per batch.
|
|
func TestOverviewLetsUpdatesIn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
asn = 1000
|
|
peer = "192.0.2.1"
|
|
otherPeer = "192.0.2.2"
|
|
// prefixCount is more prefixes than five of the walk's batches.
|
|
prefixCount = 5*walkBatch + 1
|
|
announcements = 1000
|
|
)
|
|
|
|
routeTime := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
table := New(Params{})
|
|
prefixes := make([]netip.Prefix, 0, prefixCount)
|
|
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})
|
|
prefixes = append(prefixes, prefix)
|
|
addr = addr.Next()
|
|
}
|
|
|
|
overviewDone := make(chan struct{})
|
|
|
|
go func() {
|
|
defer close(overviewDone)
|
|
|
|
table.Overview(0)
|
|
}()
|
|
|
|
// Wait until Overview holds the read lock. TryLock fails while a reader
|
|
// holds it.
|
|
for table.mu.TryLock() {
|
|
table.mu.Unlock()
|
|
runtime.Gosched()
|
|
}
|
|
|
|
for _, prefix := range prefixes[:announcements] {
|
|
table.Announce(prefix, asn, Route{Peer: otherPeer, Time: routeTime})
|
|
}
|
|
|
|
select {
|
|
case <-overviewDone:
|
|
t.Fatal("Overview returned before another peer's 1,000 announcements " +
|
|
"were applied")
|
|
default:
|
|
}
|
|
|
|
<-overviewDone
|
|
}
|