Fixes all blockers and should-fixes from the PR #16 code review.
Changes
🔴 Blocker 1 — SSE duplicate events fixed
Replaced publish() with publishLocked() (no lock acquisition) and moved the call inside the existing job.mu.Lock() block in runTraversal. This eliminates the race where a late SSE subscriber could receive the same event twice (once from replay, once from the live channel).
🔴 Blocker 2 — Request body size limit
Added r.Body = http.MaxBytesReader(w, r.Body, 1<<20) at the top of startTraversal to limit request bodies to 1 MB.
🟡 Should Fix 1 — Ticker goroutine lifecycle newHandler() now accepts a context.Context. The cleanup ticker goroutine exits when the context is cancelled. Start() creates the context and Shutdown() cancels it.
🟡 Should Fix 2 — Stale subscriber cleanup
Added unsubscribe(ch) method on TraversalJob. streamTraversal now defers job.unsubscribe(sub) so disconnected SSE clients are removed immediately.
🔵 Suggestion — ReadHeaderTimeout
Added ReadHeaderTimeout: 10 * time.Second to the http.Server to mitigate Slowloris attacks.
Verification
go vet ./... clean
go test -race ./... all pass
Fixes all blockers and should-fixes from the PR #16 code review.
## Changes
**🔴 Blocker 1 — SSE duplicate events fixed**
Replaced `publish()` with `publishLocked()` (no lock acquisition) and moved the call inside the existing `job.mu.Lock()` block in `runTraversal`. This eliminates the race where a late SSE subscriber could receive the same event twice (once from replay, once from the live channel).
**🔴 Blocker 2 — Request body size limit**
Added `r.Body = http.MaxBytesReader(w, r.Body, 1<<20)` at the top of `startTraversal` to limit request bodies to 1 MB.
**🟡 Should Fix 1 — Ticker goroutine lifecycle**
`newHandler()` now accepts a `context.Context`. The cleanup ticker goroutine exits when the context is cancelled. `Start()` creates the context and `Shutdown()` cancels it.
**🟡 Should Fix 2 — Stale subscriber cleanup**
Added `unsubscribe(ch)` method on `TraversalJob`. `streamTraversal` now defers `job.unsubscribe(sub)` so disconnected SSE clients are removed immediately.
**🔵 Suggestion — ReadHeaderTimeout**
Added `ReadHeaderTimeout: 10 * time.Second` to the `http.Server` to mitigate Slowloris attacks.
## Verification
- `go vet ./...` clean
- `go test -race ./...` all pass
- Blocker 1: move publishLocked inside job.mu to eliminate SSE duplicate-event
race between replay and live subscription
- Blocker 2: add http.MaxBytesReader (1 MB) to startTraversal to prevent
memory exhaustion from large request bodies
- Should Fix 1: thread context.Context into newHandler() and cancel it on
Server.Shutdown() to stop the ticker goroutine cleanly
- Should Fix 2: add unsubscribe() method and defer it in streamTraversal
so disconnected SSE clients don't accumulate stale channels
- Suggestion: add ReadHeaderTimeout: 10s to http.Server to mitigate Slowloris
All tests pass: go test -race ./... and go vet ./... both clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: multica-agent <github@multica.ai>
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 all blockers and should-fixes from the PR #16 code review.
Changes
🔴 Blocker 1 — SSE duplicate events fixed
Replaced
publish()withpublishLocked()(no lock acquisition) and moved the call inside the existingjob.mu.Lock()block inrunTraversal. This eliminates the race where a late SSE subscriber could receive the same event twice (once from replay, once from the live channel).🔴 Blocker 2 — Request body size limit
Added
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)at the top ofstartTraversalto limit request bodies to 1 MB.🟡 Should Fix 1 — Ticker goroutine lifecycle
newHandler()now accepts acontext.Context. The cleanup ticker goroutine exits when the context is cancelled.Start()creates the context andShutdown()cancels it.🟡 Should Fix 2 — Stale subscriber cleanup
Added
unsubscribe(ch)method onTraversalJob.streamTraversalnow defersjob.unsubscribe(sub)so disconnected SSE clients are removed immediately.🔵 Suggestion — ReadHeaderTimeout
Added
ReadHeaderTimeout: 10 * time.Secondto thehttp.Serverto mitigate Slowloris attacks.Verification
go vet ./...cleango test -race ./...all pass