Start no AS, peer or peering write after the stop deadline (closes #189)
check / check (push) Canceled after 0s

A write can run past the stop deadline while a full batch's wake-up or a
tick waits beside Stop's context. The select picked one at random, so about
half the time the AS, peer or peering handler started another write after
the deadline. Each loop now takes a waiting stop first, as the prefix
handler's does. The prefix, AS and peer handlers' flush loops were
near-copies, so they are now one function, flushLoop. A new test for each
handler fails about half its runs on the old code.

The AS and peer handler tests give the flush timer an interval longer than
the test. A timer flush during the fill can no longer hang
TestPeerHandlerWaitsAtTheMaximumBatchSize.

Model: opus-5-5
This commit was merged in pull request #191.
This commit is contained in:
clawbot committed 2026-10-27 14:56:11 +01:00
1 parent ce19b14275
commit 05b39eee7a
9 files changed
+232 -136

No files matched your search

+27 -4
View File
@@ -62,6 +62,15 @@ type PeeringHandler struct {
// periodically processes accumulated AS paths into peering records. The
// handler begins processing immediately upon creation.
func NewPeeringHandler(db database.Store, logger *logger.Logger) *PeeringHandler {
return newPeeringHandler(db, logger, peeringProcessInterval)
}
// newPeeringHandler is NewPeeringHandler with the processing timer's interval,
// processInterval, in place of peeringProcessInterval. A test gives a short
// one to have the timer start a write.
func newPeeringHandler(
db database.Store, logger *logger.Logger, processInterval time.Duration,
) *PeeringHandler {
h := &PeeringHandler{
db: db,
logger: logger,
@@ -71,7 +80,7 @@ func NewPeeringHandler(db database.Store, logger *logger.Logger) *PeeringHandler
}
// Start the periodic processing goroutine
go h.processLoop()
go h.processLoop(processInterval)
return h
}
@@ -163,15 +172,29 @@ func (h *PeeringHandler) Stop(ctx context.Context) {
}
}
// processLoop runs periodically to process AS paths into peerings, and once
// processLoop processes AS paths into peerings every processInterval, and once
// more on Stop.
func (h *PeeringHandler) processLoop() {
func (h *PeeringHandler) processLoop(processInterval time.Duration) {
defer close(h.done)
ticker := time.NewTicker(peeringProcessInterval)
ticker := time.NewTicker(processInterval)
defer ticker.Stop()
for {
// Stop goes first. A write can run past the stop deadline while a
// tick waits beside Stop's context. The select below picks one of
// them at random, but past the deadline no write may start.
select {
case ctx := <-h.stopCh:
// Final processing, unless the stop deadline has passed
if ctx.Err() == nil {
h.processPeerings()
}
return
default:
}
select {
case <-ticker.C:
h.processPeerings()