Read every route from the routing table in memory (part of #122)
check / check (push) Canceled after 0s

The IP lookups, the AS and prefix pages and their API answers, the prefix-length pages and the stats read routes only from the routing table the prefix handler keeps. fx reads the table at startup and gives it to both. AS names, WHOIS details and first-seen times still come from the database.

The IP lookup names the origin AS most peers carry, with the time of the newest kept route. The prefix answers list peers whose routes are not kept without a path or time. The stats gain origins_without_kept_route. They no longer read route times from the per-peer tables, so the two helpers that did are gone.

Model: opus-5-5
This commit was merged in pull request #194.
This commit is contained in:
clawbot committed 2026-10-27 16:21:59 +01:00
1 parent 2c304a0022
commit 506e289a63
18 files changed
+1335 -656

No files matched your search

+14 -15
View File
@@ -31,11 +31,13 @@ import (
// are disabled, since only the batched handlers exist.
var errNonBatchedNotImplemented = errors.New("non-batched handlers not implemented")
// Dependencies contains all dependencies for RouteWatch
// Dependencies contains all dependencies for RouteWatch. Table is the routing
// table read from the database at startup, which the server reads too.
type Dependencies struct {
fx.In
DB database.Store
Table *routetable.Table
Streamer *streamer.Streamer
Server *server.Server
Logger *logger.Logger
@@ -45,6 +47,7 @@ type Dependencies struct {
// RouteWatch represents the main application instance
type RouteWatch struct {
db database.Store
table *routetable.Table
streamer *streamer.Streamer
server *server.Server
logger *logger.Logger
@@ -69,6 +72,7 @@ const blocklistsDirName = "blocklists"
func New(deps Dependencies) *RouteWatch {
rw := &RouteWatch{
db: deps.DB,
table: deps.Table,
streamer: deps.Streamer,
server: deps.Server,
logger: deps.Logger,
@@ -97,13 +101,6 @@ func (rw *RouteWatch) Run(ctx context.Context) error {
if rw.config.EnableBatchedDatabaseWrites {
rw.logger.Info("Using batched database handlers for improved performance")
// The routing table is read before the streamer connects, so that
// the prefix handler applies the feed's updates to all of it.
table, err := rw.readRouteTable(ctx)
if err != nil {
return err
}
// ASHandler maintains the asns table
rw.dbHandler = NewASHandler(rw.db, rw.logger)
rw.streamer.RegisterHandler(rw.dbHandler)
@@ -114,7 +111,7 @@ func (rw *RouteWatch) Run(ctx context.Context) error {
// PrefixHandler maintains the prefixes and live_routes tables, and
// the routing table in memory and its copy in the routes table
rw.prefixHandler = NewPrefixHandler(rw.db, rw.logger, table)
rw.prefixHandler = NewPrefixHandler(rw.db, rw.logger, rw.table)
rw.prefixHandler.SetMetricsTracker(rw.streamer.GetMetricsTracker())
rw.streamer.RegisterHandler(rw.prefixHandler)
@@ -269,20 +266,21 @@ func (rw *RouteWatch) closeDatabase(ctx context.Context) {
// readRouteTable reads the routing table from the database and logs how many
// prefix and origin AS pairs and peers it holds and how long the read took.
// ctx is fx's start context, which ends 15 seconds after the start. Reading a
// full table can take longer, so the read does not stop with ctx.
func (rw *RouteWatch) readRouteTable(
ctx context.Context,
// fx calls it once, to build the server and RouteWatch, before the streamer
// connects, so the prefix handler applies the feed's updates to all of it. No
// start timeout limits the read: fx builds them before it runs a start hook.
func readRouteTable(
db database.Store, logger *logger.Logger,
) (*routetable.Table, error) {
start := time.Now()
table, err := rw.db.ReadRouteTable(context.WithoutCancel(ctx))
table, err := db.ReadRouteTable(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to read the routing table: %w", err)
}
counts := table.Counts()
rw.logger.Info("Read the routing table", "pairs", counts.Pairs,
logger.Info("Read the routing table", "pairs", counts.Pairs,
"peers", counts.Peers, "duration_ms", time.Since(start).Milliseconds())
return table, nil
@@ -299,6 +297,7 @@ func getModule() []fx.Option {
database.New,
fx.As(new(database.Store)),
),
readRouteTable,
streamer.New,
server.New,
New,
@@ -7,6 +7,7 @@ import (
"net"
"net/http"
"net/http/httptest"
"net/netip"
"slices"
"strconv"
"strings"
@@ -265,6 +266,32 @@ func (m *mockStore) GetASDetailsContext(
return m.GetASDetails(asn)
}
// GetAS returns the stored AS, or only its number when none is stored.
func (m *mockStore) GetAS(_ context.Context, asn int) (*database.ASN, bool, error) {
m.mu.Lock()
defer m.mu.Unlock()
if asnInfo, exists := m.ASNs[asn]; exists {
return asnInfo, true, nil
}
return &database.ASN{ASN: asn}, false, nil
}
// GetASWHOIS returns the stored AS, or only its number when none is stored.
func (m *mockStore) GetASWHOIS(ctx context.Context, asn int) (*database.ASN, error) {
asnInfo, _, err := m.GetAS(ctx, asn)
return asnInfo, err
}
// GetPrefixFirstSeen returns the zero time: the mock keeps no prefix times.
func (m *mockStore) GetPrefixFirstSeen(
_ context.Context, _ netip.Prefix,
) (time.Time, error) {
return time.Time{}, nil
}
// GetPrefixDetails mock implementation
func (m *mockStore) GetPrefixDetails(
_ string,
@@ -502,12 +529,15 @@ func TestRouteWatchLiveFeed(t *testing.T) {
EnableBatchedDatabaseWrites: true,
}
table := routetable.New(routetable.Params{})
// Create server
srv := server.New(mockDB, s, logger, cfg)
srv := server.New(mockDB, table, s, logger, cfg)
// Create RouteWatch with 5 second limit
deps := Dependencies{
DB: mockDB,
Table: table,
Streamer: s,
Server: srv,
Logger: logger,
@@ -626,7 +656,7 @@ func TestShutdownWritesTheUpdatesQueuedInTheStreamer(t *testing.T) {
rw := New(Dependencies{
DB: store,
Streamer: s,
Server: server.New(store, s, log, cfg),
Server: server.New(store, nil, s, log, cfg),
Logger: log,
Config: cfg,
})
@@ -728,7 +758,7 @@ func TestShutdownReturnsByItsDeadlineWhileAWriteIsBlocked(t *testing.T) {
rw := New(Dependencies{
DB: store,
Streamer: s,
Server: server.New(store, s, log, cfg),
Server: server.New(store, nil, s, log, cfg),
Logger: log,
Config: cfg,
})
@@ -842,7 +872,7 @@ func TestShutdownCheckpointsAndClosesTheDatabaseLast(t *testing.T) {
rw := New(Dependencies{
DB: store,
Streamer: s,
Server: server.New(store, s, log, cfg),
Server: server.New(store, nil, s, log, cfg),
Logger: log,
Config: cfg,
})
@@ -416,12 +416,8 @@ func TestARestartReadsTheRoutingTableBack(t *testing.T) {
var logs bytes.Buffer
rw := &RouteWatch{
db: db,
logger: &logger.Logger{Logger: slog.New(slog.NewJSONHandler(&logs, nil))},
}
table, err := rw.readRouteTable(t.Context())
table, err := readRouteTable(db,
&logger.Logger{Logger: slog.New(slog.NewJSONHandler(&logs, nil))})
if err != nil {
t.Fatalf("readRouteTable: %v", err)
}