Conversation
WalkthroughThis 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
ed784a1 to
6cb0f98
Compare
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
Outdated
Show resolved
Hide resolved
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
Outdated
Show resolved
Hide resolved
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
Outdated
Show resolved
Hide resolved
Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift
Outdated
Show resolved
Hide resolved
Sources/AblyLiveObjects/Public/Public Proxy Objects/PublicDefaultRealtimeObjects.swift
Show resolved
Hide resolved
CoreSDK callback-based
6cb0f98 to
9520905
Compare
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>
3f1d758 to
39037e7
Compare
39037e7 to
ddf0851
Compare
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>
ddf0851 to
08cc139
Compare
There was a problem hiding this comment.
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.
📒 Files selected for processing (6)
Sources/AblyLiveObjects/Internal/CoreSDK.swiftSources/AblyLiveObjects/Internal/DefaultInternalPlugin.swiftSources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swiftSources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swiftSources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swiftTests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift
| 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)) } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swiftRepository: 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.
Replace
CoreSDK'spublishandfetchServerTimemethods to use callbacks and expect to be called on the internal queue. The usage ofasyncmethods 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 andasynccode which was hard to read.Each caller now enters the internal queue once via
withCheckedContinuation+dispatchQueue.asyncand stays there through the whole operation chain, eliminating repeated queue hops.Summary by CodeRabbit