From 465e3ea2920e59006bd48499ba1caf04d4aa482c Mon Sep 17 00:00:00 2001 From: sneak Date: Wed, 14 Oct 2026 13:04:16 +0000 Subject: [PATCH] WIP: streamer Start takes a ctx; README and TODO Model: opus-5-5 --- README.md | 22 ++++++++++++++-------- TODO.md | 9 +++++++-- cmd/streamdumper/main.go | 2 +- internal/routewatch/app.go | 2 +- internal/streamer/streamer.go | 7 ++++--- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index af8ac6a..80cd801 100644 --- a/README.md +++ b/README.md @@ -273,24 +273,30 @@ standard: normalized scripts in `script/` are the entrypoints for the development workflow, and the Makefile targets are thin shims that call them. We provide: -- `script/bootstrap` — install all development dependencies (go, - golangci-lint, Go module download) +Tests and the linter run only in Docker, as the `test` and `lint` phases of the +`Dockerfile`; the image cannot be built unless both pass. Every `docker build` +in `script/` passes `--no-cache`, so each run executes every check. + +- `script/bootstrap` — install all development dependencies (git, make, go, Go + module download); warns when docker is missing - `script/setup` — make a fresh clone ready for development: runs `script/bootstrap`, then `script/install-precommit` - `script/projectname` — print the project name (used for the Docker image tag) -- `script/test` — run the test suite - (`go test -short -timeout 30s -race -cover ./...`, verbose rerun on - failure). The `-short` flag skips the live-network integration test so the - default run is deterministic and offline. -- `script/lint` — run `go vet ./...` and `golangci-lint run` +- `script/test` — build the `test` phase alone, which runs + `go test -short -timeout 90s -race -cover ./...` (verbose rerun on failure). + The `-short` flag skips the live-network integration test so the run is + deterministic and offline. +- `script/lint` — build the `lint` phase alone, which runs golangci-lint + v2.14.0 with `.golangci.yml` - `script/fmt` — format all code (writes) - `script/fmt-check` — check formatting (read-only) - `script/check` — run `script/test`, `script/lint`, and `script/fmt-check` - `script/docker` — build the Docker image tagged via `script/projectname` -- `script/cibuild` — CI entrypoint: `docker build .` +- `script/cibuild` — CI entrypoint: runs `script/bootstrap` and + `script/check`, then builds the image - `script/precommit` — pre-commit gate: `go mod tidy` + `go fmt` (must not change files), then `script/check` - `script/install-precommit` — install the git pre-commit hook that diff --git a/TODO.md b/TODO.md index d0b7920..c3c80d2 100644 --- a/TODO.md +++ b/TODO.md @@ -10,8 +10,9 @@ # Status -pre-1.0. No git tags. The Docker build runs the format check, the linter -and the tests, and the Gitea workflow runs that build on every push. The +pre-1.0. No git tags. The linter and the tests are phases of the Docker +build, and the Gitea workflow runs `script/cibuild` (the format check, both +phases, then the image) on every push. The image sets memory ceilings for a 5 GiB container (README "Memory") and the README says how to run it under upaas (README "Running under upaas"). A 35-hour run of `3898daa` on the live feed peaked at about 1 GiB, without a @@ -27,6 +28,10 @@ real 5 GiB limit (https://git.eeqj.de/sneak/routewatch/issues/3). # Completed Steps +- 2026-10-14: the shared files are re-vendored from `sneak/prompts` at + `dd4027b`: golangci-lint v2.14.0 and its configuration, lint and test as + separate uncached Dockerfile phases, agent guidance in `AGENTS.md`; the code + is fixed for what the new linter configuration finds (closes #52) - 2026-10-03: `/api/v1/stats` serves the prefix distribution from memory, seeded at startup and adjusted on every live-route write, so a request no longer reads every live route (closes #30) diff --git a/cmd/streamdumper/main.go b/cmd/streamdumper/main.go index 883c703..95a0722 100644 --- a/cmd/streamdumper/main.go +++ b/cmd/streamdumper/main.go @@ -31,7 +31,7 @@ func main() { // Start streaming before any defer: log.Fatal exits without running // deferred calls. - err := s.Start() + err := s.Start(context.Background()) if err != nil { log.Fatal("Failed to start streamer:", err) } diff --git a/internal/routewatch/app.go b/internal/routewatch/app.go index fbf1362..ccaafd2 100644 --- a/internal/routewatch/app.go +++ b/internal/routewatch/app.go @@ -109,7 +109,7 @@ func (rw *RouteWatch) Run(ctx context.Context) error { // No longer need routing table handler - PrefixHandler maintains live_routes table // Start streaming - err := rw.streamer.Start() + err := rw.streamer.Start(ctx) if err != nil { return err } diff --git a/internal/streamer/streamer.go b/internal/streamer/streamer.go index 169ebfd..223dc53 100644 --- a/internal/streamer/streamer.go +++ b/internal/streamer/streamer.go @@ -188,8 +188,9 @@ func (s *Streamer) RegisterRawHandler(handler RawMessageHandler) { // Start begins streaming BGP updates from the RIS Live API in a background goroutine. // It starts worker goroutines for each registered handler and manages automatic // reconnection with exponential backoff on connection failures. -// Returns an error if the streamer is already running. -func (s *Streamer) Start() error { +// Returns an error if the streamer is already running. It streams until Stop: +// the end of ctx does not stop it. +func (s *Streamer) Start(ctx context.Context) error { s.mu.Lock() defer s.mu.Unlock() @@ -197,7 +198,7 @@ func (s *Streamer) Start() error { return errAlreadyRunning } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(context.WithoutCancel(ctx)) s.cancel = cancel s.running = true