Files
pad/internal/server
xarmian 715ec70e94 fix(server): drain ipRateLimiter cleanup goroutines on Stop() (BUG-851) (#276)
NewRateLimiters spawned 9 ipRateLimiter cleanup goroutines per Server,
each in an unbounded `for { time.Sleep(5*time.Minute); ... }` loop with
no exit signal (middleware_ratelimit.go:78-89). Every testServer(t)
call leaked all 9, accumulating across the 210-test internal/server
suite. Under -race the goroutine count + sync overhead pushed the run
past the default 10m timeout, which is why the `Run tests with race
detector` step (gated to main pushes) has been failing on every main
run since the step was added on 2026-04-13.

This is the same flavor as BUG-842 part 2 (request-handler
fire-and-forget goroutines drained via Server.bg WaitGroup). The
rate-limiter case wasn't in BUG-842's scope: those goroutines are
spawned at construction time, not at request time, so they need a
different drain primitive.

Changes:

  - ipRateLimiter gains stopCh + stopOnce + stopWg. cleanup() rewrites
    its loop as a select over stopCh and a 5-minute ticker, deferring
    stopWg.Done(). New Stop() closes stopCh once and waits for the
    cleanup goroutine to return.
  - RateLimiters gains a Stop() that walks all 9 limiters (nil-safe
    via the (*ipRateLimiter).Stop receiver guard).
  - Server.Stop() now also calls s.rateLimiters.Stop() after
    s.bg.Wait(). Test cleanups already call Server.Stop() (added in
    BUG-842), so no test-helper changes needed.
  - New TestServer_Stop_DrainsRateLimiterCleanup pins the contract:
    construct + Stop N servers, assert runtime.NumGoroutine() returns
    to baseline ±3.
  - .github/workflows/ci.yml: bump the -race timeout from the default
    10m to 20m. The full server suite under -race takes ~13m on a dev
    laptop after the leak fix; 20m gives margin without papering over
    an actual hang. Both `Run tests with race detector` (SQLite) and
    `Run tests with race detector against PostgreSQL` are bumped.

Verified locally: go test -race -timeout=1500s ./internal/server/
finishes ok in 776s (12m57s). Without the leak fix, the same command
times out at 600s (10m) with a goroutine dump showing hundreds of
ipRateLimiter.cleanup frames.
2026-04-28 17:20:03 -04:00
..