check / check (push) Canceled after 0s
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
63 lines
1.6 KiB
Go
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
|
|
}
|
|
}
|
|
}
|