Start no AS, peer or peering write after the stop deadline (closes #189)
check / check (push) Canceled after 0s
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:
@@ -56,7 +56,6 @@ type PeerHandler struct {
|
||||
// is &mu.
|
||||
batchTaken sync.Cond
|
||||
batchFullCh chan struct{} // wakes the flush goroutine when peerBatch is full
|
||||
lastFlush time.Time // used only by the flush goroutine
|
||||
// stopCh hands Stop's context to the flush goroutine. It holds one, so
|
||||
// Stop does not wait for a write in progress to hand it over.
|
||||
stopCh chan context.Context
|
||||
@@ -75,19 +74,27 @@ type peerUpdate struct {
|
||||
// periodically flushes accumulated peer updates to the database. The handler
|
||||
// should be stopped by calling Stop when it is no longer needed.
|
||||
func NewPeerHandler(db database.Store, logger *logger.Logger) *PeerHandler {
|
||||
return newPeerHandler(db, logger, peerBatchTimeout)
|
||||
}
|
||||
|
||||
// newPeerHandler is NewPeerHandler with the flush timer's interval,
|
||||
// batchTimeout, in place of peerBatchTimeout. A test that the timer must not
|
||||
// disturb gives an interval longer than the test runs.
|
||||
func newPeerHandler(
|
||||
db database.Store, logger *logger.Logger, batchTimeout time.Duration,
|
||||
) *PeerHandler {
|
||||
h := &PeerHandler{
|
||||
db: db,
|
||||
logger: logger,
|
||||
peerBatch: make([]peerUpdate, 0, peerBatchSize),
|
||||
batchFullCh: make(chan struct{}, 1),
|
||||
lastFlush: time.Now(),
|
||||
stopCh: make(chan context.Context, 1),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
h.batchTaken.L = &h.mu
|
||||
|
||||
// Start the flush timer goroutine
|
||||
go h.flushLoop()
|
||||
go flushLoop(batchTimeout, h.batchFullCh, h.stopCh, h.done, h.flushBatch)
|
||||
|
||||
return h
|
||||
}
|
||||
@@ -173,33 +180,6 @@ func (h *PeerHandler) Stop(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// flushLoop runs in a goroutine, the only one that writes the batch: it
|
||||
// flushes when the batch is full, periodically, and once more on Stop.
|
||||
func (h *PeerHandler) flushLoop() {
|
||||
defer close(h.done)
|
||||
|
||||
ticker := time.NewTicker(peerBatchTimeout)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if time.Since(h.lastFlush) >= peerBatchTimeout {
|
||||
h.flushBatch()
|
||||
}
|
||||
case <-h.batchFullCh:
|
||||
h.flushBatch()
|
||||
case ctx := <-h.stopCh:
|
||||
// Final flush, unless the stop deadline has passed
|
||||
if ctx.Err() == nil {
|
||||
h.flushBatch()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// takeBatch returns the pending updates and leaves an empty batch in their
|
||||
// place for HandleMessage to fill. They count in writing until flushBatch has
|
||||
// written them.
|
||||
@@ -220,11 +200,11 @@ func (h *PeerHandler) takeBatch() []peerUpdate {
|
||||
}
|
||||
|
||||
// flushBatch takes the pending batch and writes it to the database, without
|
||||
// holding the mutex during the write.
|
||||
func (h *PeerHandler) flushBatch() {
|
||||
// holding the mutex during the write. It reports whether there was a batch.
|
||||
func (h *PeerHandler) flushBatch() bool {
|
||||
batch := h.takeBatch()
|
||||
if len(batch) == 0 {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// The earliest and the latest time the batch saw each peer, with the peer
|
||||
@@ -255,5 +235,5 @@ func (h *PeerHandler) flushBatch() {
|
||||
h.writing = 0
|
||||
h.mu.Unlock()
|
||||
|
||||
h.lastFlush = time.Now()
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user