Found while checking #33. Stopping the daemon while the RIS Live feed is flowing sometimes ends in panic: send on closed channel at internal/streamer/streamer.go:680, after the handlers have flushed but before Final metrics is logged. The process exits 2 and the rest of the shutdown is skipped.
Cause: Streamer.Stop cancels the stream and closes every handler queue while holding the streamer's lock. The read loop checks for cancellation only once per line, before it parses the line and hands the message to the handler queues. A line that passed that check just before Stop waits for the lock, then sends to a queue that is already closed. It is a race, so it happens on some stops and not others; it does not depend on how the entrypoint starts the daemon.
Definition of done
Stopping the daemon while the feed is flowing never panics: the log reaches Final metrics and the process exits 0.
A test covers a stop that races with a message being handed to the handler queues.
make check stays green (in the Docker build).
Model: opus-5-5
Found while checking https://git.eeqj.de/sneak/routewatch/issues/33. Stopping the daemon while the RIS Live feed is flowing sometimes ends in `panic: send on closed channel` at `internal/streamer/streamer.go:680`, after the handlers have flushed but before `Final metrics` is logged. The process exits 2 and the rest of the shutdown is skipped.
Cause: `Streamer.Stop` cancels the stream and closes every handler queue while holding the streamer's lock. The read loop checks for cancellation only once per line, before it parses the line and hands the message to the handler queues. A line that passed that check just before `Stop` waits for the lock, then sends to a queue that is already closed. It is a race, so it happens on some stops and not others; it does not depend on how the entrypoint starts the daemon.
## Definition of done
- Stopping the daemon while the feed is flowing never panics: the log reaches `Final metrics` and the process exits 0.
- A test covers a stop that races with a message being handed to the handler queues.
- `make check` stays green (in the Docker build).
Model: opus-5-5
Implementer's notes, on top of the definition of done above:
Stop cancels the stream and closes the handler queues under the streamer's write lock (internal/streamer/streamer.go, Stop), and the read loop sends to the queues under the read lock. So a check made under that same read lock, just before the sends, sees either a streamer that is still running or one that has stopped, never half of each. Returning there is enough; no new lock, channel or flag is needed.
Stop's comment says it is safe to call more than once, but a second call closes the queues again, which also panics. Make that true in the same change, since the definition of done is that stopping never panics.
The test drives Stop while messages are being handed to the queues and passes under -race; it must not touch the network.
Model: opus-5-5
Implementer's notes, on top of the definition of done above:
- `Stop` cancels the stream and closes the handler queues under the streamer's write lock (`internal/streamer/streamer.go`, `Stop`), and the read loop sends to the queues under the read lock. So a check made under that same read lock, just before the sends, sees either a streamer that is still running or one that has stopped, never half of each. Returning there is enough; no new lock, channel or flag is needed.
- `Stop`'s comment says it is safe to call more than once, but a second call closes the queues again, which also panics. Make that true in the same change, since the definition of done is that stopping never panics.
- The test drives `Stop` while messages are being handed to the queues and passes under `-race`; it must not touch the network.
Model: opus-5-5
clawbot
self-assigned this 2026-09-28 21:09:04 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while checking #33. Stopping the daemon while the RIS Live feed is flowing sometimes ends in
panic: send on closed channelatinternal/streamer/streamer.go:680, after the handlers have flushed but beforeFinal metricsis logged. The process exits 2 and the rest of the shutdown is skipped.Cause:
Streamer.Stopcancels the stream and closes every handler queue while holding the streamer's lock. The read loop checks for cancellation only once per line, before it parses the line and hands the message to the handler queues. A line that passed that check just beforeStopwaits for the lock, then sends to a queue that is already closed. It is a race, so it happens on some stops and not others; it does not depend on how the entrypoint starts the daemon.Definition of done
Final metricsand the process exits 0.make checkstays green (in the Docker build).Model: opus-5-5
Implementer's notes, on top of the definition of done above:
Stopcancels the stream and closes the handler queues under the streamer's write lock (internal/streamer/streamer.go,Stop), and the read loop sends to the queues under the read lock. So a check made under that same read lock, just before the sends, sees either a streamer that is still running or one that has stopped, never half of each. Returning there is enough; no new lock, channel or flag is needed.Stop's comment says it is safe to call more than once, but a second call closes the queues again, which also panics. Make that true in the same change, since the definition of done is that stopping never panics.Stopwhile messages are being handed to the queues and passes under-race; it must not touch the network.Model: opus-5-5
PR: #36
Model: opus-5-5