fix(project_events): use process-lifetime ctx for async SSE publish #8

Merged
oleks merged 1 commits from fix/sse-detach-ctx into main 2026-05-16 00:10:52 +03:00
Owner

Follow-up to #7.

Bug

SSE board events never reached browsers. Root cause: the fire-and-forget publish goroutine inherited the HTTP request's context via context.WithoutCancel(ctx). That context carries a request-scoped DB session that is returned to the pool the moment the handler completes. By the time the goroutine ran GetProjectByID + per-uid access checks, the session was gone, so connectedUIDsWithProjectAccess errored out and returned an empty recipient set — every event silently dropped (uids=[] connected=[N]). The error only logged at Debug level, so it was invisible at the default Info level.

Fix

detach() now returns graceful.GetManager().ShutdownContext() — a process-lifetime context backed by the global engine, cancelled on app shutdown so goroutines don't leak past teardown. The session tag is already resolved synchronously before the goroutine starts, so no request-scoped values are needed in the goroutine.

Verification

Deployed to git.oleks.space and confirmed live: triggering a card move now logs uids=[2] connected=[2] and the card relocates in a second browser tab with no refresh. All 15 services/project_events unit tests pass; go build, go vet clean.

One-line diff in spirit: context.WithoutCancel(ctx)graceful.GetManager().ShutdownContext() (plus import + doc comment).

Follow-up to #7. ## Bug SSE board events never reached browsers. Root cause: the fire-and-forget publish goroutine inherited the HTTP request's context via `context.WithoutCancel(ctx)`. That context carries a request-scoped DB session that is returned to the pool the moment the handler completes. By the time the goroutine ran `GetProjectByID` + per-uid access checks, the session was gone, so `connectedUIDsWithProjectAccess` errored out and returned an empty recipient set — every event silently dropped (`uids=[] connected=[N]`). The error only logged at Debug level, so it was invisible at the default Info level. ## Fix `detach()` now returns `graceful.GetManager().ShutdownContext()` — a process-lifetime context backed by the global engine, cancelled on app shutdown so goroutines don't leak past teardown. The session tag is already resolved synchronously before the goroutine starts, so no request-scoped values are needed in the goroutine. ## Verification Deployed to git.oleks.space and confirmed live: triggering a card move now logs `uids=[2] connected=[2]` and the card relocates in a second browser tab with no refresh. All 15 `services/project_events` unit tests pass; `go build`, `go vet` clean. One-line diff in spirit: `context.WithoutCancel(ctx)` → `graceful.GetManager().ShutdownContext()` (plus import + doc comment).
oleks added 1 commit 2026-05-16 00:10:47 +03:00
The publish goroutine inherited the request context via
context.WithoutCancel. That context carries a request-scoped DB
session returned to the pool when the HTTP handler completes, so
GetProjectByID + access checks in the goroutine raced with session
teardown and intermittently returned empty recipient sets (uids=[]),
silently dropping every SSE board event. Root the detached context in
graceful.ShutdownContext() (global engine, process lifetime).

(cherry picked from commit bfc10289e6)
oleks added this to the (deleted) project 2026-05-16 00:10:51 +03:00
oleks merged commit 4676a3af93 into main 2026-05-16 00:10:52 +03:00
oleks moved this to Closed in Issues on 2026-05-16 10:21:42 +03:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: oleks/gitea#8