Skip to content

Make CoreSDK callback-based#119

Open
lawrence-forooghian wants to merge 2 commits intomainfrom
make-CoreSDK-use-callbacks
Open

Make CoreSDK callback-based#119
lawrence-forooghian wants to merge 2 commits intomainfrom
make-CoreSDK-use-callbacks

Conversation

@lawrence-forooghian
Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian commented Feb 27, 2026

Replace CoreSDK's publish and fetchServerTime methods to use callbacks and expect to be called on the internal queue. The usage of async methods was not a good mix with the rest of this plugin's heavy usage of the internal queue as its synchronisation mechanism, and led to a lot of switching between normal code and async code which was hard to read.

Each caller now enters the internal queue once via withCheckedContinuation + dispatchQueue.async and stays there through the whole operation chain, eliminating repeated queue hops.

Summary by CodeRabbit

  • Refactor
    • Updated internal asynchronous operation handling to use callback-based completion patterns for publish and server time fetching operations.
    • Modified related operations in counters, maps, and object creation to align with the callback-based architecture.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Walkthrough

This pull request converts callback-based asynchronous operations in the Ably Live Objects SDK from async/throws patterns to non-blocking callback signatures. Protocol methods and implementations are refactored to use Result-type callbacks routed through internal queues, with consumer code bridged via CheckedContinuation for external async compatibility.

Changes

Cohort / File(s) Summary
Core Protocol & Implementation
Sources/AblyLiveObjects/Internal/CoreSDK.swift, Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift, Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift
Protocol methods publish and fetchServerTime converted from async throws to callback-based with Result<Void/Date, ARTErrorInfo>. DefaultCoreSDK routes callbacks to internal queue; DefaultInternalPlugin's sendObject refactored to callback pattern. Mock implementations updated to dispatch callbacks on channel state queue.
Live Object Types
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift, Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
Methods increment, set, and remove rewritten with CheckedContinuation wrappers. Input validation added for counter amount finiteness. Publish operations now use nosync\_publish callbacks routed through continuations.
Live Objects Container
Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
Methods createMap, createCounter, and testsOnly_publish refactored to use CheckedContinuation with nested callback handling. Server time fetching via nosync\_fetchServerTime and publish via nosync\_publish now route results through continuation resumption.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Callbacks flow like a gentle stream,
No async await in this new scheme,
Continuations bridge the old and new,
Results wrapped tight, the pattern's true!
Hops forward now, non-blocking and lean. 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Make CoreSDK callback-based' directly and clearly summarizes the main change: converting CoreSDK methods from async/throws to callback-based APIs, which is the central objective of this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch make-CoreSDK-use-callbacks

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 and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/119/AblyLiveObjects February 27, 2026 16:58 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/119/AblyLiveObjects February 27, 2026 17:23 Inactive
@lawrence-forooghian lawrence-forooghian changed the title Make CoreSDK callback-based Make CoreSDK callback-based Feb 27, 2026
@github-actions github-actions bot temporarily deployed to staging/pull/119/AblyLiveObjects February 27, 2026 19:12 Inactive
The doc comment introduced in aa552c4 said "If set to true" but the
property is an optional closure, not a boolean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lawrence-forooghian lawrence-forooghian force-pushed the make-CoreSDK-use-callbacks branch 2 times, most recently from 3f1d758 to 39037e7 Compare February 27, 2026 19:30
@github-actions github-actions bot temporarily deployed to staging/pull/119/AblyLiveObjects February 27, 2026 19:31 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/119/AblyLiveObjects February 27, 2026 19:40 Inactive
Replace CoreSDK's `publish` and `fetchServerTime` methods to use
callbacks and expect to be called on the internal queue. The usage of
`async` methods was not a good mix with the rest of this plugin's heavy
usage of the internal queue as its synchronisation mechanism, and led to
a lot of switching between normal code and `async` code which was hard
to read.

Each caller now enters the internal queue once via
withCheckedContinuation + dispatchQueue.async and stays there through
the whole operation chain, eliminating repeated queue hops.

I've left the overridable publish implementations (used by the tests) as
`async` functions, because Claude ran into all sorts of concurrency
checking issues when trying to port the JS integration tests to use
callbacks for this, and I didn't really feel like trying to fix it (nor
do these implementations need access to the plugin's internal state at
the moment so there's no compelling reason for them to do their work on
the internal queue).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift`:
- Around line 18-28: The read of _publishHandler in nosync_publish is not
synchronized with the write in setPublishHandler, causing a race; fix by reading
_publishHandler under the same channelStateMutex lock (use
channelStateMutex.withLock or equivalent) to capture the handler into a local
variable before creating the Task, then bail out if nil; refer to
nosync_publish, setPublishHandler, _publishHandler, and channelStateMutex when
making the change.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between db28b94 and 08cc139.

📒 Files selected for processing (6)
  • Sources/AblyLiveObjects/Internal/CoreSDK.swift
  • Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
  • Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift
  • Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift

Comment on lines +18 to +28
func nosync_publish(objectMessages: [OutboundObjectMessage], callback: @escaping @Sendable (Result<Void, ARTErrorInfo>) -> Void) {
if let handler = _publishHandler {
try await handler(objectMessages)
let queue = channelStateMutex.dispatchQueue
Task {
do throws(ARTErrorInfo) {
try await handler(objectMessages)
queue.async { callback(.success(())) }
} catch {
queue.async { callback(.failure(error)) }
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift

Repository: ably/ably-liveobjects-swift-plugin

Length of output: 2471


Guard _publishHandler reads with mutex.withLock.

_publishHandler is written under lock in setPublishHandler (line 44-45) but read without lock in nosync_publish (line 19). This asymmetric synchronization can cause race conditions and test flakiness.

Suggested fix
func nosync_publish(objectMessages: [OutboundObjectMessage], callback: `@escaping` `@Sendable` (Result<Void, ARTErrorInfo>) -> Void) {
-    if let handler = _publishHandler {
+    let handler = mutex.withLock { _publishHandler }
+    if let handler {
         let queue = channelStateMutex.dispatchQueue
         Task {
             do throws(ARTErrorInfo) {
                 try await handler(objectMessages)
                 queue.async { callback(.success(())) }
             } catch {
                 queue.async { callback(.failure(error)) }
             }
         }
     } else {
         protocolRequirementNotImplemented()
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift` around lines 18 - 28, The
read of _publishHandler in nosync_publish is not synchronized with the write in
setPublishHandler, causing a race; fix by reading _publishHandler under the same
channelStateMutex lock (use channelStateMutex.withLock or equivalent) to capture
the handler into a local variable before creating the Task, then bail out if
nil; refer to nosync_publish, setPublishHandler, _publishHandler, and
channelStateMutex when making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant