Skip to content

feat: expose clock injection and set explicit AsyncStream buffering policies - #1351

Draft
grdsdev wants to merge 1 commit into
mainfrom
guilhermesouza/sdk-1799-streams-and-tasks-set-asyncstream-buffering-policies-own
Draft

grdsdev wants to merge 1 commit into
mainfrom
guilhermesouza/sdk-1799-streams-and-tasks-set-asyncstream-buffering-policies-own

Conversation

@grdsdev

@grdsdev grdsdev commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Closes the three parts of SDK-1799 that hold up against the code, and records why the fourth does not.

Clock injection is public now

clock is now on SupabaseClientOptions.GlobalOptions, RealtimeClientOptions and
AuthClient.Configuration, all defaulting to ContinuousClock(). Consumers can drive
time-dependent behaviour with a TestClock instead of waiting out real seconds. The two
package-visibility clock initializers this replaces are gone.

Auth is routed through that clock for the first time: the auto-refresh loop in SessionManager
was still on Task.sleep(nanoseconds:), and RetryRequestInterceptor was still on a default
ContinuousClock(). A caller passing a test clock would otherwise have found half of Auth
ignoring it.

Additive and source-compatible — new defaulted parameters only, so no V3_MIGRATION.md entry.

Buffering policies are explicit, and all stay unbounded

bufferingPolicy is now set at all 14 makeStream() sites with a stated reason. Every one stays
.unbounded, deliberately rather than by omission.

The issue proposed .bufferingNewest(1) for status and heartbeat. That breaks three existing
tests and the pattern they document:

  • RealtimeLifecycleTests.swift:133 already carries the comment "Subscribe before the OS close
    so the buffered .disconnected event is not missed even when .reconnecting follows
    immediately"
    , then does statusUpdates.first { $0 == .disconnected }. Under
    .bufferingNewest(1), .connecting lands right behind .disconnected and evicts it.
  • RealtimeTests.swift:527 and :621 collect heartbeat statuses and assert the sequence
    [.sent, .ok]. .ok follows .sent within one ack, so .sent is dropped.

Three more streams carry control flow and must stay unbounded regardless: ChannelStateManager
waits for .subscribed/.unsubscribed, ConnectionManager's observer latches on
.reconnecting, and URLSessionWebSocket.events carries phx_reply frames. The rest carry
server payloads, where a bounded policy is silent data loss.

Task ownership: no change, because there is no leak

I audited all 15 sites the issue lists. The long-lived tasks are already owned and cancelled in
deinit (heartbeatTask, messageTask, stateObserverTask, pendingDisconnectTask).
URLSessionWebSocket._scheduleReceive is unowned but self-terminating — close() cancels the
task, the pending receive() throws, the loop ends. Everything else is a one-shot hop that
completes in milliseconds; AuthClient.deinit's task must outlive self by design. Full audit
is on the Linear issue.

Tests

  • SessionManagerTests.autoRefreshTicksOnTheInjectedClock drives a full auto-refresh tick on a
    TestClock in 0.025s, where the real tick is 30s.
  • SupabaseClientTests.globalClockReachesAuthAndRealtime asserts the caller's clock instance
    reaches both sub-clients.
  • waitUntil moved from Tests/RealtimeTests/TestSupport.swift into Sources/TestHelpers so the
    Auth suite reuses it rather than duplicating it.
━ Test run with 1502 tests in 148 suites passed after 5.913 seconds with 1 known issue.

swift-format lint --recursive --strict Sources Tests, ./scripts/test-docs.sh and
./scripts/spell-check.sh are all clean.

Reviewer notes

Start with Sources/Supabase/SupabaseClient.swift and Sources/Auth/Internal/SessionManager.swift
— that is the whole behavioural change. The 14 buffering sites are comments plus an explicit
argument, no behaviour change.

One flake worth knowing about: my first version of the auto-refresh test stopped the loop in a
fire-and-forget defer { Task { ... } }. LiveSessionManager resolves Dependencies[clientID]
on every access, so a loop still running at test exit refreshed against the next test's
transport. I caught it, and the stop is awaited now.

Fixes SDK-1799

…olicies

Adds `clock` to `SupabaseClientOptions.GlobalOptions`, `RealtimeClientOptions`
and `AuthClient.Configuration`, all defaulting to `ContinuousClock()`, so
consumers can drive time-dependent behaviour with a `TestClock` instead of
waiting out real seconds. The two `package`-visibility clock initializers this
replaces are gone.

Routes Auth through that clock: the auto-refresh loop in `SessionManager` was
still on `Task.sleep(nanoseconds:)`, and `RetryRequestInterceptor` was still on
a default `ContinuousClock()`, so a caller passing a test clock would have found
half of Auth ignoring it.

Sets `bufferingPolicy` explicitly at all 14 `makeStream()` sites. Every one
stays `.unbounded`, now deliberately and with a stated reason rather than by
omission: three of these streams carry control flow (`ChannelStateManager`
waits for `.subscribed`, `ConnectionManager`'s observer latches on
`.reconnecting`, `URLSessionWebSocket.events` carries `phx_reply` frames) and
the rest carry server payloads, so a bounded policy would either hang a waiter
or silently drop caller data.

Also moves `waitUntil` from `Tests/RealtimeTests/TestSupport.swift` into
`Sources/TestHelpers` so the Auth suite can reuse it.

Fixes SDK-1799

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

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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