Fixes the two goroutine leaks from U6 of the memory work in #3 (analysis comment dated 2026-09-21 14:34). Brief: #12
Stats handlers: handleStatusJSON and handleStats ran the database stats query in a goroutine that sent on an unbuffered channel. When the request timeout won, nothing received and the goroutine blocked forever; the status page polls every 2s, so once the query exceeds the timeout every poll leaked one goroutine. Both channels now have capacity 1, so the send always completes whether or not the handler is still listening.
Streamer: each connection started two ticker goroutines bound to the streamer's lifetime context, so every reconnect left two behind. They now use a per-connection context cancelled when the stream call returns.
Tests force the stats timeout repeatedly and drive many reconnects, then assert the goroutine count settles back to its starting value (plain runtime.NumGoroutine with a short settle loop).
Not visible in the diff:
The streamer gained an unexported endpoint field, defaulting to the live RIS URL, so a test can point a stream call at a local server; production behaviour is unchanged.
The reconnect test calls stream directly rather than through the backoff loop, so it stays offline and fast.
Host make lint panics on a toolchain mismatch (host linter built with go1.25, stdlib needs go1.26); the gate ran green inside the Docker lint stage with the pinned linter.
Model: opus-4-8
Fixes the two goroutine leaks from U6 of the memory work in https://git.eeqj.de/sneak/routewatch/issues/3 (analysis comment dated 2026-09-21 14:34). Brief: https://git.eeqj.de/sneak/routewatch/issues/12
Stats handlers: `handleStatusJSON` and `handleStats` ran the database stats query in a goroutine that sent on an unbuffered channel. When the request timeout won, nothing received and the goroutine blocked forever; the status page polls every 2s, so once the query exceeds the timeout every poll leaked one goroutine. Both channels now have capacity 1, so the send always completes whether or not the handler is still listening.
Streamer: each connection started two ticker goroutines bound to the streamer's lifetime context, so every reconnect left two behind. They now use a per-connection context cancelled when the `stream` call returns.
Tests force the stats timeout repeatedly and drive many reconnects, then assert the goroutine count settles back to its starting value (plain `runtime.NumGoroutine` with a short settle loop).
Not visible in the diff:
- The streamer gained an unexported endpoint field, defaulting to the live RIS URL, so a test can point a `stream` call at a local server; production behaviour is unchanged.
- The reconnect test calls `stream` directly rather than through the backoff loop, so it stays offline and fast.
- Host `make lint` panics on a toolchain mismatch (host linter built with go1.25, stdlib needs go1.26); the gate ran green inside the Docker lint stage with the pinned linter.
Model: opus-4-8
The stats handlers ran the database query in a goroutine that sent on an
unbuffered channel. When the 4s request timeout won, nothing received and
the goroutine blocked forever; the status page polls every 2s, so once the
query exceeds the timeout every poll leaked one goroutine. Give both
channels capacity 1 so the send always completes.
The streamer started two ticker goroutines per connection that exited only
with the streamer's lifetime context, leaking two on every reconnect. Scope
them to a per-connection context cancelled when the stream call returns.
Tests force the stats timeout repeatedly and drive many reconnects, then
assert the goroutine count settles back to its starting value. The streamer
gains an internal endpoint field so a test can point it at a local server.
Model: opus-4-8
PASS: both goroutine leaks are fixed as specified (stats channels buffered to capacity 1; the two per-connection ticker goroutines scoped to a context cancelled when stream returns), the two tests genuinely fail when the fix is reverted and pass with it, the gate is green in the Docker build, the branch rebases cleanly on the current next, and scope, commit title, and trailers all conform.
Model: opus-4-8
PASS: both goroutine leaks are fixed as specified (stats channels buffered to capacity 1; the two per-connection ticker goroutines scoped to a context cancelled when `stream` returns), the two tests genuinely fail when the fix is reverted and pass with it, the gate is green in the Docker build, the branch rebases cleanly on the current `next`, and scope, commit title, and trailers all conform.
Model: opus-4-8
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.
Fixes the two goroutine leaks from U6 of the memory work in #3 (analysis comment dated 2026-09-21 14:34). Brief: #12
Stats handlers:
handleStatusJSONandhandleStatsran the database stats query in a goroutine that sent on an unbuffered channel. When the request timeout won, nothing received and the goroutine blocked forever; the status page polls every 2s, so once the query exceeds the timeout every poll leaked one goroutine. Both channels now have capacity 1, so the send always completes whether or not the handler is still listening.Streamer: each connection started two ticker goroutines bound to the streamer's lifetime context, so every reconnect left two behind. They now use a per-connection context cancelled when the
streamcall returns.Tests force the stats timeout repeatedly and drive many reconnects, then assert the goroutine count settles back to its starting value (plain
runtime.NumGoroutinewith a short settle loop).Not visible in the diff:
streamcall at a local server; production behaviour is unchanged.streamdirectly rather than through the backoff loop, so it stays offline and fast.make lintpanics on a toolchain mismatch (host linter built with go1.25, stdlib needs go1.26); the gate ran green inside the Docker lint stage with the pinned linter.Model: opus-4-8
PASS: both goroutine leaks are fixed as specified (stats channels buffered to capacity 1; the two per-connection ticker goroutines scoped to a context cancelled when
streamreturns), the two tests genuinely fail when the fix is reverted and pass with it, the gate is green in the Docker build, the branch rebases cleanly on the currentnext, and scope, commit title, and trailers all conform.Model: opus-4-8