Files
routewatch/internal/routewatch/batchwrite.go
T
clawbot 2c304a0022
check / check (push) Canceled after 0s
Make a timed batch write on every tick (closes #192)
The prefix, AS and peer handlers skipped a timed write unless one
interval had passed since the last write ended. The next tick came a
little less than one interval after that, so every other tick was
skipped. Each tick now writes. A full batch's write restarts the timer,
so the next timed write comes one interval after that write began.
Comparing clock readings would not do: two ticks one interval apart can
read a hair under it. flushBatch no longer reports whether it wrote a
batch, since nothing reads that. A new test runs the loop on a fake
clock and fails on the old code.

Model: opus-5-5
2026-10-27 15:23:52 +01:00

63 lines
1.6 KiB
Go

package routewatch
import (
"context"
"time"
)
// flushLoop is the flush goroutine of the prefix, AS and peer handlers. It is
// the only goroutine that writes a handler's batch. flushBatch takes the batch
// and writes it.
//
// flushLoop calls flushBatch on each tick of a timer, every batchTimeout. It
// also calls it at once when batchFullCh wakes it. A full batch's write
// restarts the timer, so the next tick comes batchTimeout after that write
// began. When stopCh hands it Stop's context, it calls flushBatch once more,
// unless the stop deadline has passed. Then it closes done and returns.
func flushLoop(
batchTimeout time.Duration,
batchFullCh <-chan struct{},
stopCh <-chan context.Context,
done chan<- struct{},
flushBatch func(),
) {
defer close(done)
ticker := time.NewTicker(batchTimeout)
defer ticker.Stop()
for {
// Stop goes first. 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
// below picks one of them at random, but past the deadline no flush
// may start.
select {
case ctx := <-stopCh:
// Final flush, unless the stop deadline has passed
if ctx.Err() == nil {
flushBatch()
}
return
default:
}
select {
case <-ticker.C:
flushBatch()
case <-batchFullCh:
// Reset also drops a tick that waits in ticker.C, so no timed
// write follows this one at once.
ticker.Reset(batchTimeout)
flushBatch()
case ctx := <-stopCh:
// Final flush, unless the stop deadline has passed
if ctx.Err() == nil {
flushBatch()
}
return
}
}
}