Skip to content

fix(server): subscription hardening — read-error subscription preservation + owner_id fence (PR #22 follow-ups)#24

Merged
Calmingstorm merged 1 commit into
mainfrom
fix/subscribe-hardening
Jul 15, 2026
Merged

fix(server): subscription hardening — read-error subscription preservation + owner_id fence (PR #22 follow-ups)#24
Calmingstorm merged 1 commit into
mainfrom
fix/subscribe-hardening

Conversation

@Calmingstorm

Copy link
Copy Markdown
Owner

Closes the two non-blocking NITs surfaced during PR #22's review.

1. Transient read error no longer drops live subscriptions

SubscribeAuthorizedStable called RemoveChannel on any read error, dropping the channel's entire subscriber set — including legit members a concurrent connect had installed — stranding already-connected clients until reconnect. It now returns the error and leaves the set intact (the caller broadcasts nothing; the committed row self-heals on the client's next channel-list fetch). RemoveChannel is kept only on the genuine !exists (deleted) path.

2. Migration 019 — fence owner_id updates

authorizedMemberIDs reads servers.owner_id, but migration 017 fenced the servers table on DELETE only. An ownership transfer (UPDATE servers SET owner_id) would change channel-visibility authorization without taking the exclusive per-server lock. No endpoint updates owner_id today, so the trigger fires never until one is added — at which point the create-vs-authz race is already closed. Future-proofing, zero current cost.

Tests

Mutation-verified pins for both: TestSubscribeAuthorizedStableReadErrorPreservesSubscriptions (realtime) and TestMigration019FencesServerOwnerUpdate (up/down trigger-event assertion). Full Go -race suite + gofmt/vet/golangci-lint green.

…review

Two non-blocking items Odin flagged while reviewing the concurrency work:

1. A transient read error in SubscribeAuthorizedStable no longer tears down the
   channel's live subscriptions. RemoveChannel on an indeterminate read dropped
   legit subscribers a concurrent connect had installed, stranding already-
   connected members until reconnect. It now returns the error and leaves the
   set intact; the caller broadcasts nothing and the committed row self-heals on
   the client's next channel-list fetch. RemoveChannel is kept only on the
   genuine !exists path.

2. Migration 019 extends the per-server event fence on the servers table from
   DELETE-only to also cover owner_id UPDATE. An ownership transfer changes
   channel-visibility authorization; no endpoint updates owner_id today, so the
   trigger fires never until one is added -- at which point the create-vs-authz
   race is already closed by construction. Future-proofing, zero current cost.

Mutation-verified pins for both. Full Go -race suite + gofmt/vet/lint green.
@Calmingstorm

Copy link
Copy Markdown
Owner Author

LGTM at 10eda697b838b1d4f61679884407dd6e5e749057.

I reviewed the complete PR #24 diff and found no blocking issues.

Verified:

  • SubscribeAuthorizedStable now returns a transient authorization/existence read error without calling RemoveChannel, preserving the existing live subscriber set; the genuine !exists path still removes the channel.
  • The read-error regression exercises the behavior directly and fails under the old teardown behavior.
  • Migration 019 replaces the server DELETE-only trigger with BEFORE UPDATE OF owner_id OR DELETE, and the down migration restores DELETE-only behavior.
  • In addition to the committed trigger-metadata regression, I ran a temporary PostgreSQL concurrency probe: an owner_id update blocked while the matching shared server-event fence was held, then completed after release.
  • Migration 019 upgrade and rollback behavior passed against PostgreSQL 16.

Validation:

  • Full Go suite with -race against PostgreSQL 16 and Redis 7
  • Targeted regressions repeated five times
  • go vet ./...
  • gofmt and diff checks
  • GolangCI with --new-from-rev=origin/main: 0 new issues
  • GitHub Go, Lint, and Web jobs green
  • PR is mergeable; exact checkout clean

PR #24 is ready to merge.

@Calmingstorm Calmingstorm merged commit 6d4981e into main Jul 15, 2026
3 checks passed
@Calmingstorm Calmingstorm deleted the fix/subscribe-hardening branch July 15, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant