• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-swift / 24844914410
81%

Build:
DEFAULT BRANCH: main
Ran 23 Apr 2026 03:58PM UTC
Jobs 1
Files 89
Run time 1min
Badge
Embed ▾
README BADGES
x

If you need to use a raster PNG badge, change the '.svg' to '.png' in the link

Markdown

Textile

RDoc

HTML

Rst

23 Apr 2026 03:50PM UTC coverage: 80.992% (+0.08%) from 80.912%
24844914410

push

github

web-flow
fix(realtime): resolve subscribe/unsubscribe lifecycle races via ChannelStateManager actor (#974)

* refactor(realtime): extract ChannelStateManager actor for subscription lifecycle

Moves the subscribe/unsubscribe state machine out of RealtimeChannelV2 and
into a dedicated ChannelStateManager actor, mirroring the ConnectionManager
pattern introduced in #855.

The actor owns the full lifecycle (unsubscribed → subscribing → subscribed
→ unsubscribing → unsubscribed): retries, timeouts, server-confirmation
waits, and deduplication of concurrent calls. Network side-effects
(phx_join, phx_leave, makeRef, ensure-connected) are injected as closures,
keeping the state machine independent of the concrete channel.

Key behaviours:
- Synchronous stateDidChange callback forwards transitions to the channel's
  status subject without an async hop, so status reads right after
  subscribe() returns see the latest value.
- beginSubscribe uses withTaskCancellationHandler so cancelling the caller
  cancels the in-flight retry loop (previously Task.value kept waiting).
- runUnsubscribe waits (bounded by timeoutInterval) for the server's
  phx_close when coming from .subscribed, matching pre-refactor semantics.
  When aborting an in-flight subscribe there's no live session, so the
  wait is skipped.

Net result: RealtimeChannelV2.swift shrinks by 154 lines; 16 new unit
tests cover the state machine directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(realtime): close three ChannelStateManager races found in review

- Move `clientChanges` buffer from the actor to a `LockIsolated` on the
  channel. `onPostgresChange` now writes synchronously, eliminating a
  fire-and-forget `Task` that could lose the race against a subsequent
  `subscribe()` (causing `phx_join` to ship without any filter).

- Make push registration atomic against `joinRef`. `push()` no longer
  does two actor hops (`joinRef` then `storePush`); it calls a single
  `storePushIfJoinRefMat... (continued)

304 of 344 new or added lines in 2 files covered. (88.37%)

4 existing lines in 1 file now uncovered.

7103 of 8770 relevant lines covered (80.99%)

30.46 hits per line

Uncovered Changes

Lines Coverage ∆ File
30
88.24
Sources/Realtime/ChannelStateManager.swift
10
70.32
-2.72% Sources/Realtime/RealtimeChannelV2.swift

Coverage Regressions

Lines Coverage ∆ File
4
70.32
-2.72% Sources/Realtime/RealtimeChannelV2.swift
Jobs
ID Job ID Ran Files Coverage
1 24844914410.1 23 Apr 2026 03:58PM UTC 89
80.99
GitHub Action Run
Source Files on build 24844914410
  • Tree
  • List 89
  • Changed 1
  • Source Changed 1
  • Coverage Changed 1
Coverage ∆ File Lines Relevant Covered Missed Hits/Line
  • Back to Repo
  • Github Actions Build #24844914410
  • 81307624 on github
  • Prev Build on main (#24838124728)
  • Next Build on main (#24883910750)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc