Drain the handler queues before stopping the handlers on shutdown (closes #69)
check / check (push) Canceled after 0s

Shutdown stopped the handlers before the streamer, so the updates still
on the handler queues reached handlers that no longer wrote them. It now
stops the streamer first. Streamer.Stop puts the STATE messages it holds
for a full queue on that queue, closes the queues and returns once every
worker has handed its handler everything on its queue; then each handler
writes what it holds. PeeringHandler.Stop now waits for its loop's final
write instead of racing it.

Streamer.SetURL lets the shutdown test read from a local server.

Model: opus-5-5
This commit is contained in:
2026-10-19 10:56:58 +00:00
parent 2712183f21
commit 6c6b05103f
6 changed files with 235 additions and 26 deletions
+45 -7
View File
@@ -255,6 +255,24 @@ func TestHeldStateMessagesKeepTheirPlaceInTheQueue(t *testing.T) {
}
}
// takeAll takes the messages off queue, as a handler's worker would, and sends
// them on the returned channel once queue is closed.
func takeAll(queue chan *ristypes.RISMessage) chan []*ristypes.RISMessage {
taken := make(chan []*ristypes.RISMessage, 1)
go func() {
var msgs []*ristypes.RISMessage
for msg := range queue {
msgs = append(msgs, msg)
}
taken <- msgs
}()
return taken
}
// newStreamerWithFullStateQueue returns a streamer reading from url with one
// handler, a stateHandler whose queue is full.
func newStreamerWithFullStateQueue(url string) *Streamer {
@@ -306,20 +324,23 @@ func TestOtherHandlersKeepReceivingWhileAStateMessageIsHeld(t *testing.T) {
}
}
// TestStopReturnsAtOnceWithStateMessagesHeld has the server send two STATE
// messages and hold the response open while the queue of the handler that
// wants them is full. Stop must return at once and end the stream.
func TestStopReturnsAtOnceWithStateMessagesHeld(t *testing.T) {
// TestStopQueuesTheHeldStateMessages has the server send two STATE messages
// and an UPDATE and hold the response open while the queue of the handler that
// wants STATE messages is full, so both are held. Stop must put both on the
// queue, after the messages already on it, before it closes the queue, and
// must end the stream.
func TestStopQueuesTheHeldStateMessages(t *testing.T) {
t.Parallel()
const (
lines = 2
held = 2
lines = held + 1
maxWait = 5 * time.Second
)
srv := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, stateDownLine+stateDownLine)
_, _ = io.WriteString(w, stateDownLine+stateDownLine+updateLine)
flush(w)
<-r.Context().Done()
@@ -328,7 +349,7 @@ func TestStopReturnsAtOnceWithStateMessagesHeld(t *testing.T) {
s := newStreamerWithFullStateQueue(srv.URL)
// The raw handler sees each line before the read loop hands it to the
// queues, so once it has seen the second line, the first is held.
// queues, so once it has seen the UPDATE, both STATE messages are held.
linesSeen := make(chan struct{}, lines)
s.RegisterRawHandler(func(string) { linesSeen <- struct{}{} })
@@ -350,6 +371,9 @@ func TestStopReturnsAtOnceWithStateMessagesHeld(t *testing.T) {
}
}
// No worker runs here: the test takes from the full queue in its place.
queue := s.handlers[0].queue
taken := takeAll(queue)
stopped := make(chan struct{})
go func() {
@@ -364,6 +388,20 @@ func TestStopReturnsAtOnceWithStateMessagesHeld(t *testing.T) {
maxWait)
}
// Stop has closed the queue, so takeAll is done or about to be.
msgs := <-taken
if len(msgs) != cap(queue)+held {
t.Fatalf("%d messages came off the queue, want the %d on it and the "+
"%d held", len(msgs), cap(queue), held)
}
for _, msg := range msgs[cap(queue):] {
if msg.Peer != stateDownPeer || msg.State != ristypes.StateDown {
t.Errorf("message after those on the queue has peer %q, state %q; "+
"want the down from %s", msg.Peer, msg.State, stateDownPeer)
}
}
select {
case err := <-streamErr:
if !errors.Is(err, context.Canceled) {