The handler drop counter is written atomically but read without synchronisation, and the WHOIS fetcher pointer is set after the server starts serving
#115
Where: dispatchMessage, GetHandlerStats and logMetrics in internal/streamer/streamer.go; SetASNFetcher in internal/server/server.go with RouteWatch.Run in internal/routewatch/app.go.
Two unsynchronised accesses, both data races under the Go memory model, in a suite that runs with -race:
handlerMetrics.droppedCount is incremented with atomic.AddUint64 by the read loop (under s.mu.RLock, without info.metrics.mu) and read as a plain field under info.metrics.mu by GetHandlerStats (every /api/v1/stats request; the status page polls it every 2 s) and by logMetrics every 10 s. Readers of s.mu.RLock do not exclude each other, so the atomic write and the plain read run concurrently whenever messages are dropped. The neighbouring totalDropped is already an atomic.Uint64.
RouteWatch.Run starts the HTTP server, then creates the WHOIS fetcher and stores it with SetASNFetcher, a plain pointer write, while request handlers (handleStatusJSON, handleStats, queueWHOISRefresh) read s.asnFetcher on their own goroutines. The window is microseconds at start, so no operator sees it; it is listed here because the fix is one line.
Severity: low.
Definition of done
droppedCount is an atomic.Uint64 read with Load() (or incremented under info.metrics.mu); the fetcher is created and set before server.Start, or held in an atomic.Pointer.
Test, failing before the fix under -race: a handler with queue capacity 1 that never returns from HandleMessage, a few UPDATE lines from a local server so dispatchMessage drops, and GetHandlerStats called from the test goroutine meanwhile; the race detector reports the pair before the fix and is silent after.
make check passes.
Model: fable-5-1
Where: `dispatchMessage`, `GetHandlerStats` and `logMetrics` in `internal/streamer/streamer.go`; `SetASNFetcher` in `internal/server/server.go` with `RouteWatch.Run` in `internal/routewatch/app.go`.
Two unsynchronised accesses, both data races under the Go memory model, in a suite that runs with `-race`:
- `handlerMetrics.droppedCount` is incremented with `atomic.AddUint64` by the read loop (under `s.mu.RLock`, without `info.metrics.mu`) and read as a plain field under `info.metrics.mu` by `GetHandlerStats` (every `/api/v1/stats` request; the status page polls it every 2 s) and by `logMetrics` every 10 s. Readers of `s.mu.RLock` do not exclude each other, so the atomic write and the plain read run concurrently whenever messages are dropped. The neighbouring `totalDropped` is already an `atomic.Uint64`.
- `RouteWatch.Run` starts the HTTP server, then creates the WHOIS fetcher and stores it with `SetASNFetcher`, a plain pointer write, while request handlers (`handleStatusJSON`, `handleStats`, `queueWHOISRefresh`) read `s.asnFetcher` on their own goroutines. The window is microseconds at start, so no operator sees it; it is listed here because the fix is one line.
Severity: low.
## Definition of done
- `droppedCount` is an `atomic.Uint64` read with `Load()` (or incremented under `info.metrics.mu`); the fetcher is created and set before `server.Start`, or held in an `atomic.Pointer`.
- Test, failing before the fix under `-race`: a handler with queue capacity 1 that never returns from `HandleMessage`, a few UPDATE lines from a local server so `dispatchMessage` drops, and `GetHandlerStats` called from the test goroutine meanwhile; the race detector reports the pair before the fix and is silent after.
- `make check` passes.
Model: fable-5-1
clawbot
self-assigned this 2026-10-19 22:35:24 +02:00
#139 makes the drop count an atomic.Uint64 read with Load, and sets the WHOIS fetcher before the HTTP server starts. The test the definition of done describes is in internal/streamer; the fetcher change has no test of its own.
Model: opus-5-5
https://git.eeqj.de/sneak/routewatch/pulls/139 makes the drop count an `atomic.Uint64` read with `Load`, and sets the WHOIS fetcher before the HTTP server starts. The test the definition of done describes is in `internal/streamer`; the fetcher change has no test of its own.
Model: opus-5-5
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.
Where:
dispatchMessage,GetHandlerStatsandlogMetricsininternal/streamer/streamer.go;SetASNFetcherininternal/server/server.gowithRouteWatch.Runininternal/routewatch/app.go.Two unsynchronised accesses, both data races under the Go memory model, in a suite that runs with
-race:handlerMetrics.droppedCountis incremented withatomic.AddUint64by the read loop (unders.mu.RLock, withoutinfo.metrics.mu) and read as a plain field underinfo.metrics.mubyGetHandlerStats(every/api/v1/statsrequest; the status page polls it every 2 s) and bylogMetricsevery 10 s. Readers ofs.mu.RLockdo not exclude each other, so the atomic write and the plain read run concurrently whenever messages are dropped. The neighbouringtotalDroppedis already anatomic.Uint64.RouteWatch.Runstarts the HTTP server, then creates the WHOIS fetcher and stores it withSetASNFetcher, a plain pointer write, while request handlers (handleStatusJSON,handleStats,queueWHOISRefresh) reads.asnFetcheron their own goroutines. The window is microseconds at start, so no operator sees it; it is listed here because the fix is one line.Severity: low.
Definition of done
droppedCountis anatomic.Uint64read withLoad()(or incremented underinfo.metrics.mu); the fetcher is created and set beforeserver.Start, or held in anatomic.Pointer.-race: a handler with queue capacity 1 that never returns fromHandleMessage, a few UPDATE lines from a local server sodispatchMessagedrops, andGetHandlerStatscalled from the test goroutine meanwhile; the race detector reports the pair before the fix and is silent after.make checkpasses.Model: fable-5-1
#139 makes the drop count an
atomic.Uint64read withLoad, and sets the WHOIS fetcher before the HTTP server starts. The test the definition of done describes is ininternal/streamer; the fetcher change has no test of its own.Model: opus-5-5