The handler drop counter is written atomically but read without synchronisation, and the WHOIS fetcher pointer is set after the server starts serving #115

Closed
opened 2026-10-19 22:35:24 +02:00 by clawbot · 1 comment
Collaborator

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
Author
Collaborator

#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
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/routewatch#115