-
Notifications
You must be signed in to change notification settings - Fork 7
[ECO-5245] Convert public API and internals to @MainActor
#261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe changes refactor the SDK’s concurrency model by adding guidelines that enforce isolation of mutable state to the main actor. Many components, including public APIs, example code, core protocols, and tests, have been updated to use the Changes
Sequence Diagram(s)sequenceDiagram
participant UI as User Interface (MainActor)
participant API as ChatAPI (MainActor)
participant Client as ChatClient (MainActor)
participant Room as Room (Synchronous)
participant Sub as Subscription
UI->>API: Initialize ChatAPI
API->>Client: createChatClient() (MainActor)
Client->>Room: Create/attach room synchronously
Room->>Sub: Subscribe to messages/status updates
Sub-->>UI: Deliver updates on main thread
Assessment against linked issues
Possibly related issues
Possibly related PRs
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
cc30e24 to
e2591cf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🔭 Outside diff range comments (2)
Example/AblyChatExample/Mocks/MockClients.swift (1)
119-123: 💡 Verification agent🧩 Analysis chain
Method signature synchronized but inconsistent with implementation
The
subscribemethod is now synchronous, which aligns with the PR's goal. However, there appears to be an inconsistency: according to the PR summary,Messages.subscribeshould remain async, but this mock implementation is synchronous. This might be intentional for the mock, but it's worth verifying.
🏁 Script executed:
#!/bin/bash # Check the Messages protocol to see if subscribe is still expected to be async rg -A 3 "func subscribe.*BufferingPolicy" --type swift | grep -e "async"Length of output: 517
Action Required: Align Mock Signature with Asynchronous Interface
The production implementation of
subscribe(bufferingPolicy:)is defined as an asynchronous function (inSources/AblyChat/Messages.swiftand related files), whereas the mock implementation inExample/AblyChatExample/Mocks/MockClients.swiftremains synchronous. This inconsistency can lead to confusion or integration issues.
- Location:
Example/AblyChatExample/Mocks/MockClients.swift, lines 119–123- Issue: The method signature in the mock does not reflect the
async throwsnature of the production interface.- Recommendation: Update the mock implementation to be asynchronous (e.g., add
async throwsto its signature) or document the reasoning for intentionally deviating from the production signature.Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)
96-104: 🛠️ Refactor suggestionMethods that modify state might need thread safety consideration
These methods modify mutable state and were previously protected by actor isolation. Now they could potentially be called concurrently from different threads, leading to race conditions.
Consider one of these approaches:
- Add
@MainActorannotation to these methods- Ensure the mock is only accessed from a single thread in tests
- Add explicit synchronization (like a lock) if concurrent access is expected
+@MainActor func attach() async throws(InternalError) { attachCallCount += 1 // ... } +@MainActor func publish(_ name: String?, data: JSONValue?, extras: [String: JSONValue]?) { lastMessagePublishedName = name lastMessagePublishedExtras = extras lastMessagePublishedData = data }Also applies to: 110-118, 179-183, 193-204, 206-208
🧹 Nitpick comments (7)
Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift (1)
19-19: Validate append-only usage ofemitDiscontinuityArguments.
Appending toemitDiscontinuityArgumentsis straightforward for single-thread usage. If you plan to run these tests concurrently, confirm that this array isn’t mutated from multiple threads at the same time without synchronization.Tests/AblyChatTests/DefaultMessagesTests.swift (3)
68-68: Cross-check for removed concurrency dependencies.
Confirm that dependencies used bydefaultMessagesinitialization no longer requireawait.
151-151: Confirm that the@Sendableclosure captures only thread-safe resources.
Similar to line 115, ensure the closure’s captured data is safely shareable across concurrency boundaries.
167-168: Check ordering foronDiscontinuitysubscription vs. event emission.
These two lines are tightly coupled: the subscription is created, then the event is immediately emitted. Ensure the test verifies real-world timing (sometimes discontinuities arrive before subscription).Sources/AblyChat/RoomReactions.swift (1)
34-35: Documentation needs updating to match synchronous behavior.The method documentation still mentions an "AsyncSequence" which suggests asynchronous behavior, but the method is now synchronous. The documentation should be updated to accurately reflect the current behavior.
Sources/AblyChat/DefaultTyping.swift (1)
251-251: Consider concurrency safety for EventTracker struct.
SinceEventTrackeris accessing mutable state across Task boundaries, consider making it an actor or confining its mutation strictly to the main actor context.Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)
11-11: Nonisolated annotations are now unnecessary but harmlessNow that the class is no longer an actor, the
nonisolatedkeywords on these methods don't serve a functional purpose since classes don't have isolation boundaries by default.You could remove these
nonisolatedannotations for cleaner code, but keeping them doesn't cause any harm and might document that these methods were intentionally designed to be callable from any context.Also applies to: 59-61, 142-154, 156-177
🛑 Comments failed to post (1)
Sources/AblyChat/DefaultTyping.swift (1)
65-65:
⚠️ Potential issuePotential concurrency concern with eventTracker mutation in Task.
Although this class is marked@MainActor, usingTask { ... }by default may run on a background thread, creating a data race oneventTracker. To keep mutations on the main actor, wrap this operation inTask { @MainActor in ... }or useMainActor.run:Task { - let currentEventID = eventTracker.updateEventID() + await MainActor.run { + let currentEventID = eventTracker.updateEventID() + // proceed with logic here + } }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.Task { await MainActor.run { let currentEventID = eventTracker.updateEventID() // proceed with logic here } }
e2591cf to
6cf993e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (5)
Tests/AblyChatTests/Mocks/MockChannels.swift (1)
4-26: Consider explicitly marking the class with @mainactorWhile the removal of synchronization mechanisms is consistent with the PR's approach, it would be clearer to explicitly mark this class with
@MainActorto ensure all future usage remains on the main actor. This would make the concurrency model more explicit and prevent potential misuse.-final class MockChannels: InternalRealtimeChannelsProtocol { +@MainActor +final class MockChannels: InternalRealtimeChannelsProtocol {Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (4)
11-11: Nonisolated keyword is redundant when not in actor contextThe
nonisolatedkeyword is specific to actor isolation and doesn't have the same effect in a class. Since this has been converted from an actor to a class, this keyword may be unnecessary.- nonisolated var properties: ARTChannelProperties { .init(attachSerial: attachSerial, channelSerial: channelSerial) } + var properties: ARTChannelProperties { .init(attachSerial: attachSerial, channelSerial: channelSerial) }
59-61: Remove nonisolated keyword from all methodsSimilar to the
propertiesgetter, thenonisolatedkeyword is redundant in a class context.- nonisolated var underlying: any RealtimeChannelProtocol { + var underlying: any RealtimeChannelProtocol { fatalError("Not implemented") }
142-154: Remove nonisolated from subscribe methodThe
nonisolatedkeyword should be removed from this method as it's no longer in an actor context.- nonisolated func subscribe(_: String, callback: @escaping ARTMessageCallback) -> ARTEventListener? { + func subscribe(_: String, callback: @escaping ARTMessageCallback) -> ARTEventListener? {
156-158: Clean up remaining nonisolated keywordsRemove the
nonisolatedkeyword from all remaining methods as they're redundant in a class context.This applies to the following methods:
unsubscribe(_:)on line 156- Both
on(_:callback:)methods on lines 160 and 164off(_:)on line 168nameproperty getter on line 172Also applies to: 160-166, 168-170, 172-177
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (53)
CONTRIBUTING.md(2 hunks)Example/AblyChatExample/ContentView.swift(9 hunks)Example/AblyChatExample/Mocks/MockClients.swift(11 hunks)Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift(1 hunks)README.md(1 hunks)Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift(5 hunks)Sources/AblyChat/ChatAPI.swift(1 hunks)Sources/AblyChat/ChatClient.swift(4 hunks)Sources/AblyChat/Connection.swift(1 hunks)Sources/AblyChat/DefaultConnection.swift(3 hunks)Sources/AblyChat/DefaultMessages.swift(4 hunks)Sources/AblyChat/DefaultOccupancy.swift(4 hunks)Sources/AblyChat/DefaultPresence.swift(5 hunks)Sources/AblyChat/DefaultRoomLifecycleContributor.swift(1 hunks)Sources/AblyChat/DefaultRoomReactions.swift(3 hunks)Sources/AblyChat/DefaultTyping.swift(10 hunks)Sources/AblyChat/EmitsDiscontinuities.swift(2 hunks)Sources/AblyChat/Messages.swift(3 hunks)Sources/AblyChat/Occupancy.swift(3 hunks)Sources/AblyChat/Presence.swift(4 hunks)Sources/AblyChat/Room.swift(14 hunks)Sources/AblyChat/RoomFeature.swift(2 hunks)Sources/AblyChat/RoomLifecycleManager.swift(15 hunks)Sources/AblyChat/RoomReactions.swift(3 hunks)Sources/AblyChat/Rooms.swift(6 hunks)Sources/AblyChat/SimpleClock.swift(1 hunks)Sources/AblyChat/Subscription.swift(2 hunks)Sources/AblyChat/SubscriptionStorage.swift(1 hunks)Sources/AblyChat/TimerManager.swift(1 hunks)Sources/AblyChat/Typing.swift(3 hunks)Tests/AblyChatTests/ChatAPITests.swift(1 hunks)Tests/AblyChatTests/DefaultChatClientTests.swift(1 hunks)Tests/AblyChatTests/DefaultMessagesTests.swift(7 hunks)Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift(100 hunks)Tests/AblyChatTests/DefaultRoomOccupancyTests.swift(3 hunks)Tests/AblyChatTests/DefaultRoomReactionsTests.swift(5 hunks)Tests/AblyChatTests/DefaultRoomTests.swift(15 hunks)Tests/AblyChatTests/DefaultRoomsTests.swift(12 hunks)Tests/AblyChatTests/IntegrationTests.swift(14 hunks)Tests/AblyChatTests/Mocks/MockChannels.swift(2 hunks)Tests/AblyChatTests/Mocks/MockConnection.swift(1 hunks)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift(2 hunks)Tests/AblyChatTests/Mocks/MockInternalRealtimeClientFactory.swift(1 hunks)Tests/AblyChatTests/Mocks/MockRealtime.swift(3 hunks)Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift(1 hunks)Tests/AblyChatTests/Mocks/MockRoom.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomFactory.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift(1 hunks)Tests/AblyChatTests/Mocks/MockSimpleClock.swift(1 hunks)Tests/AblyChatTests/SubscriptionStorageTests.swift(3 hunks)Tests/AblyChatTests/SubscriptionTests.swift(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (35)
- Tests/AblyChatTests/ChatAPITests.swift
- README.md
- Tests/AblyChatTests/DefaultChatClientTests.swift
- Tests/AblyChatTests/Mocks/MockConnection.swift
- Sources/AblyChat/DefaultRoomLifecycleContributor.swift
- Tests/AblyChatTests/SubscriptionStorageTests.swift
- Tests/AblyChatTests/DefaultMessagesTests.swift
- Sources/AblyChat/TimerManager.swift
- Tests/AblyChatTests/IntegrationTests.swift
- CONTRIBUTING.md
- Sources/AblyChat/ChatAPI.swift
- Tests/AblyChatTests/Mocks/MockInternalRealtimeClientFactory.swift
- Tests/AblyChatTests/DefaultRoomTests.swift
- Tests/AblyChatTests/Mocks/MockSimpleClock.swift
- Sources/AblyChat/Connection.swift
- Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift
- Sources/AblyChat/Messages.swift
- Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift
- Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift
- Tests/AblyChatTests/DefaultRoomOccupancyTests.swift
- Sources/AblyChat/Typing.swift
- Sources/AblyChat/Occupancy.swift
- Tests/AblyChatTests/SubscriptionTests.swift
- Sources/AblyChat/DefaultPresence.swift
- Sources/AblyChat/RoomReactions.swift
- Sources/AblyChat/RoomFeature.swift
- Sources/AblyChat/SimpleClock.swift
- Sources/AblyChat/Subscription.swift
- Sources/AblyChat/EmitsDiscontinuities.swift
- Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift
- Sources/AblyChat/ChatClient.swift
- Sources/AblyChat/DefaultRoomReactions.swift
- Sources/AblyChat/DefaultOccupancy.swift
- Sources/AblyChat/DefaultTyping.swift
- Example/AblyChatExample/ContentView.swift
🧰 Additional context used
🧬 Code Definitions (12)
Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (7)
Sources/AblyChat/RoomFeature.swift (1)
onDiscontinuity(69-71)Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
onDiscontinuity(19-21)Sources/AblyChat/DefaultMessages.swift (2)
onDiscontinuity(48-50)onDiscontinuity(205-207)Tests/AblyChatTests/DefaultMessagesTests.swift (1)
onDiscontinuity(156-173)Sources/AblyChat/DefaultOccupancy.swift (2)
onDiscontinuity(24-26)onDiscontinuity(91-93)Sources/AblyChat/DefaultRoomReactions.swift (2)
onDiscontinuity(30-32)onDiscontinuity(126-128)Sources/AblyChat/DefaultTyping.swift (2)
onDiscontinuity(32-34)onDiscontinuity(211-213)
Tests/AblyChatTests/DefaultRoomsTests.swift (6)
Sources/AblyChat/Rooms.swift (3)
testsOnly_hasRoomMapEntryWithID(303-313)release(316-354)testsOnly_subscribeToOperationWaitEvents(145-147)Sources/AblyChat/Room.swift (1)
release(418-425)Tests/AblyChatTests/DefaultRoomTests.swift (1)
release(268-290)Tests/AblyChatTests/Mocks/MockRoom.swift (1)
release(55-62)Sources/AblyChat/RoomLifecycleManager.swift (1)
testsOnly_subscribeToOperationWaitEvents(581-583)Tests/AblyChatTests/Mocks/MockRoomFactory.swift (1)
setRoom(12-14)
Tests/AblyChatTests/Mocks/MockChannels.swift (6)
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)
get(82-89)Example/AblyChatExample/Mocks/MockRealtime.swift (3)
get(86-88)get(256-258)get(260-262)Sources/AblyChat/DefaultMessages.swift (2)
get(32-34)get(172-178)Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (3)
get(144-147)get(268-284)get(286-302)Sources/AblyChat/DefaultOccupancy.swift (2)
get(20-22)get(81-88)Sources/AblyChat/DefaultPresence.swift (2)
get(16-18)get(20-22)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
Sources/AblyChat/RoomLifecycleManager.swift (1)
onRoomStatusChange(297-299)
Sources/AblyChat/DefaultConnection.swift (4)
Sources/AblyChat/TimerManager.swift (3)
hasRunningTask(24-26)cancelTimer(19-22)setTimer(7-17)Sources/AblyChat/SubscriptionStorage.swift (1)
emit(43-47)Sources/AblyChat/Subscription.swift (1)
emit(72-79)Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (2)
off(247-249)off(400-402)
Tests/AblyChatTests/DefaultRoomReactionsTests.swift (8)
Sources/AblyChat/DefaultRoomReactions.swift (4)
subscribe(26-28)subscribe(69-123)onDiscontinuity(30-32)onDiscontinuity(126-128)Sources/AblyChat/RoomReactions.swift (1)
subscribe(45-47)Sources/AblyChat/DefaultMessages.swift (4)
subscribe(28-30)subscribe(76-169)onDiscontinuity(48-50)onDiscontinuity(205-207)Sources/AblyChat/RoomFeature.swift (1)
onDiscontinuity(69-71)Sources/AblyChat/EmitsDiscontinuities.swift (1)
onDiscontinuity(34-36)Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
emitDiscontinuity(15-17)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (1)
emitDiscontinuity(21-23)Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift (1)
emitDiscontinuity(19-21)
Sources/AblyChat/Rooms.swift (2)
Sources/AblyChat/Room.swift (1)
createRoom(150-159)Tests/AblyChatTests/Mocks/MockRoomFactory.swift (1)
createRoom(16-25)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (6)
Sources/AblyChat/RoomLifecycleManager.swift (6)
testsOnly_subscribeToHandledContributorStateChanges(350-352)testsOnly_subscribeToHandledTransientDisconnectTimeouts(378-380)createManager(41-50)performAttachOperation(712-714)performAttachOperation(716-718)onRoomStatusChange(297-299)Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift (1)
createManager(11-14)Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (3)
performAttachOperation(19-29)onRoomStatusChange(54-56)waitToBeAbleToPerformPresenceOperations(62-64)Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)
emitStateChange(206-208)Sources/AblyChat/RoomFeature.swift (1)
waitToBeAbleToPerformPresenceOperations(73-75)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (1)
waitToBeAbleToPerformPresenceOperations(25-35)
Sources/AblyChat/DefaultMessages.swift (7)
Tests/AblyChatTests/DefaultMessagesTests.swift (1)
onDiscontinuity(156-173)Sources/AblyChat/DefaultRoomReactions.swift (2)
onDiscontinuity(30-32)onDiscontinuity(126-128)Sources/AblyChat/RoomFeature.swift (1)
onDiscontinuity(69-71)Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
onDiscontinuity(19-21)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (1)
onDiscontinuity(17-19)Sources/AblyChat/DefaultOccupancy.swift (2)
onDiscontinuity(24-26)onDiscontinuity(91-93)Sources/AblyChat/DefaultTyping.swift (2)
onDiscontinuity(32-34)onDiscontinuity(211-213)
Sources/AblyChat/Presence.swift (2)
Sources/AblyChat/DefaultPresence.swift (4)
subscribe(52-54)subscribe(56-58)subscribe(261-280)subscribe(282-307)Sources/AblyChat/DefaultRoomReactions.swift (2)
subscribe(26-28)subscribe(69-123)
Example/AblyChatExample/Mocks/MockClients.swift (6)
Sources/AblyChat/Room.swift (2)
onStatusChange(110-112)onStatusChange(429-431)Sources/AblyChat/Connection.swift (1)
onStatusChange(36-38)Tests/AblyChatTests/Mocks/MockRoom.swift (1)
onStatusChange(43-45)Sources/AblyChat/Presence.swift (2)
subscribe(130-132)subscribe(134-136)Sources/AblyChat/DefaultRoomReactions.swift (2)
subscribe(26-28)subscribe(69-123)Sources/AblyChat/DefaultTyping.swift (2)
subscribe(16-18)subscribe(55-115)
Sources/AblyChat/RoomLifecycleManager.swift (8)
Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift (1)
emitDiscontinuity(19-21)Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
emitDiscontinuity(15-17)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (2)
emitDiscontinuity(21-23)waitToBeAbleToPerformPresenceOperations(25-35)Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
onRoomStatusChange(54-56)waitToBeAbleToPerformPresenceOperations(62-64)Sources/AblyChat/RoomFeature.swift (1)
waitToBeAbleToPerformPresenceOperations(73-75)Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift (1)
createManager(11-14)Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)
subscribeToState(193-204)Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (1)
subscribeToState(251-258)
🔇 Additional comments (119)
Sources/AblyChat/SubscriptionStorage.swift (6)
6-7: Good application of@MainActorto ensure thread safety.Converting this class from
@unchecked Sendableto@MainActoris a solid improvement. This change aligns perfectly with the PR's goal of isolating mutable state to the main actor, eliminating the need for manual synchronization mechanisms. The@MainActorannotation ensures that all operations onsubscriptionsare performed on the main thread, making the code safer and more maintainable.
21-21: Removal of locks simplifies the code.The direct assignment to the
subscriptionsdictionary without explicit locking is now safe due to the@MainActorannotation on the class. This change simplifies the code while maintaining thread safety guarantees.
24-26: Proper use of@MainActorin the termination handler.Using
Task { @MainActor in ... }ensures that the subscription termination handling occurs on the main thread, which is necessary since it modifies thesubscriptionsdictionary. The use of a task allows the termination handler to be non-blocking while still enforcing the actor isolation.
34-35: Simplified property access with actor isolation.Direct access to
subscriptions.countwithout locking is now safe due to the@MainActorannotation. This simplification makes the code cleaner and easier to maintain.
39-39: Safe dictionary modification without explicit locking.Directly modifying the
subscriptionsdictionary insubscriptionDidTerminatewithout explicit locking is safe due to the@MainActorannotation. This change simplifies the code and maintains thread safety.
44-46: Simplified iteration over subscriptions.Direct iteration over
subscriptionswithout locking in theemitmethod is now safe due to the@MainActorannotation. This change removes the need for complex synchronization code while maintaining thread safety.Sources/AblyChat/DefaultConnection.swift (8)
8-10: Great job usinginternal private(set)for encapsulationThese property modifications align well with the PR objective of isolating mutable state to the main actor. By using
private(set), you're ensuring the properties can only be modified within the class while still allowing them to be read externally.
15-16: Good synchronous initializationNice work simplifying the initialization by directly setting the properties instead of using asynchronous calls. This makes the code cleaner and more efficient.
42-42: Proper removal ofawaitfrom timer checkCorrectly removed the
awaitkeyword fromtimerManager.hasRunningTask()to match the method's now synchronous implementation.
52-52: Synchronous timer cancellationGood conversion of the timer cancellation to a synchronous call, which matches the updated
TimerManagerimplementation.
55-56: Consistent state updatesThese direct state updates ensure that local state matches the emitted status change. Well-structured approach to maintaining consistency.
61-69: Improved timer handler implementationThe refactored timer handling code correctly captures the weak reference to
timerManagerand directly updates the properties onself. This is a good implementation of the transient disconnect timer logic.
80-82: Essential state updates after emitting status changeImportant addition of these state updates after emitting the status change. This ensures the internal state is always consistent with what's been communicated to subscribers.
86-88: Good use of@MainActorfor connection cleanupExcellent use of
Task { @MainActor in ... }to ensure the event listener is unregistered on the main actor. This aligns with the PR's objective of isolating mutable state changes to the main actor.Tests/AblyChatTests/Mocks/MockChannels.swift (4)
4-4: Removed@unchecked Sendableconformance aligns with MainActor isolation strategyThe removal of
@unchecked Sendableis consistent with the PR's approach of isolating mutable state to the main actor instead of relying on explicit thread safety mechanisms. This simplifies the class's concurrency model.
6-7: Property renaming improves clarity and simplifies accessRenaming the properties from
_getArgumentsand_releaseArgumentstogetArgumentsandreleaseArgumentsremoves the underscore prefix, which is a good practice for properties that are meant to be accessed via their property wrapper (private(set)). The inline initialization also simplifies the code.
14-14: Removed mutex synchronization for property accessThe code now directly appends to the
getArgumentsarray without using a mutex lock. This is safe if all access to this class is confined to the main actor, which aligns with the PR's strategy of main-actor isolation.
24-24: Removed mutex synchronization for property accessSimilar to the
getArgumentsproperty, the code now directly appends to thereleaseArgumentsarray without using a mutex lock. This is consistent with the PR's approach to simplify concurrency by isolating mutable state to the main actor.Tests/AblyChatTests/Mocks/MockRoom.swift (3)
4-4: Transformation from actor to class aligns with main actor pattern.The change from
actor MockRoomtoclass MockRoomis consistent with the PR's objective to isolate mutable state to the main actor rather than using individual actors. This simplifies the concurrency model across the SDK.
43-43: API simplification by removing async keyword.Removing the
asynckeyword fromonStatusChangesimplifies the API and improves the user experience by making the method synchronous. This is consistent with the PR's goal to reduce unnecessary asynchronous operations.
4-70:❓ Verification inconclusive
Verify thread safety in the mock implementation.
With the switch from
actortoclass, automatic protection against concurrent access to mutable state is removed. Since the class has mutable state (releaseCallCount), ensure any test using this mock handles concurrency appropriately or only accesses it from the main thread.This change appears to be part of the broader strategy to centralize concurrency control at the
@MainActorlevel rather than using individual actors.
🏁 Script executed:
#!/bin/bash # Check how this mock is used in tests to ensure thread safety # Look for places where MockRoom is instantiated echo "Searching for MockRoom instantiations:" rg "MockRoom\(" Tests/ -A 2 # Look for places where releaseCallCount is accessed echo "\nSearching for releaseCallCount access:" rg "releaseCallCount" Tests/ -A 2Length of output: 5500
Thread Safety in the MockRoom Implementation
The switch from an actor to a class means that the built‐in concurrency protection is no longer present. Our search shows that:
- In tests (e.g., in Tests/AblyChatTests/DefaultRoomsTests.swift),
MockRoomis instantiated and itsrelease()method is called in a sequential, single-threaded manner.- Checks on
releaseCallCount(e.g., assertions like#expect(roomToReturn.releaseCallCount == 1)) suggest that the current usage assumes controlled, non-concurrent access.Action Items:
- Confirm that all invocations of
release()are executed on the main thread or within a context that serializes access.- If there's any possibility of concurrent calls, introduce appropriate synchronization or limit access to ensure thread safety.
- Remember that this change aligns with the project’s strategy of centralizing concurrency control at the
@MainActorlevel.Tests/AblyChatTests/Mocks/MockRealtime.swift (4)
6-6: Implementation successfully matches the new concurrency modelThe class definition has correctly been kept as a
final classwhile implementing theInternalRealtimeClientProtocol. This change aligns with the PR objective of refactoring the SDK's concurrency model by simplifying property access and removing specialized actor types.
12-13: Synchronization mechanism successfully removedThe refactoring correctly removes the previous synchronization mechanism, replacing the mutex-guarded private properties with directly accessible
private(set)properties. This aligns with moving state management to the main actor as described in the PR objectives.
32-32: Simplified direct property accessThe direct append to
requestArgumentswithout mutex locking represents a clean implementation of the new concurrency model, where synchronization is handled by the main actor rather than explicit locks.
48-48: Direct property assignment without synchronizationThe direct assignment to
createWrapperSDKProxyOptionsArgumentwithout synchronization is consistent with the new concurrency approach where thread safety is managed through the main actor.Example/AblyChatExample/Mocks/MockClients.swift (11)
4-4: Actor successfully converted to classThe
MockChatClienthas been successfully converted from an actor to a class, aligning with the PR objective of simplifying the concurrency model by isolating mutable state to the main actor instead of using actor types.
22-22: Actor successfully converted to classThe
MockRoomsactor has been correctly converted to a class as part of the broader effort to refactor the SDK's concurrency model and simplify state management.
44-53: Room implementation properly refactoredThe
MockRoomclass has been successfully converted from an actor, with properties properly marked asnonisolatedwhere appropriate, which allows them to be accessed from any context safely in the new concurrency model.
58-62: Property initialization refactored correctlyThe direct initialization of properties in the constructor replaces previous lazy initialization patterns, which is appropriate for the new synchronous access pattern and main actor isolation model.
83-83: Synchronous method signature is appropriateThe
onStatusChangemethod now correctly uses a synchronous signature, consistent with the relevant code snippets fromRoom.swiftwhere the method returns a subscription without requiring asynchronous execution.
88-88: Messages implementation properly converted and synchronizedThe
MockMessagesclass and itssubscribemethod have been successfully converted from asynchronous actor patterns to synchronous class methods, matching the simplified concurrency model.Also applies to: 119-119
188-188: MockRoomReactions properly implements synchronous subscriptionThe
MockRoomReactionsclass and itssubscribemethod successfully follow the new pattern of synchronous operation, consistent with the changes inDefaultRoomReactions.swift.Also applies to: 226-226
235-235: MockTyping properly converted to synchronous class implementationThe
MockTypingclass and itssubscribemethod correctly implement the synchronous pattern, aligned with the changes inDefaultTyping.swift.Also applies to: 257-257
278-278: MockPresence successfully converted with correct method signaturesThe
MockPresenceclass has been properly converted from an actor, with thesubscribemethods correctly implemented as synchronous methods that match the signatures from thePresence.swiftinterface.Also applies to: 385-391
398-398: MockOccupancy properly implements the new concurrency modelThe
MockOccupancyclass and itssubscribemethod correctly follow the simplified synchronous pattern, which is consistent with the PR's objective of moving state management to the main actor.Also applies to: 418-418
431-446: MockConnection implementation correctly refactoredThe
MockConnectionclass properly implements theConnectionprotocol with the appropriate synchronous method signatures. TheonStatusChangemethod is correctly marked asnonisolatedto indicate thread safety.Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (4)
4-4: Impact of switching from actor to class on concurrency safetyChanging from
final actortofinal classremoves the automatic isolation guarantees that Swift actors provide. Since this is part of the broader initiative to consolidate mutable state to the main actor, ensure proper thread safety is maintained for this mock class.Consider if any explicit
@MainActorannotations are needed on the class or specific methods to maintain thread safety, especially for methods that modify state likepublish(_:data:extras:)on line 179 andemitStateChange(_:)on line 206.
20-29: Review continued need for @unchecked Sendable with actor removalNow that the class is no longer an actor, the
@unchecked Sendableannotation might be less critical, though still important if this struct is passed across thread boundaries. The TODO comment mentions revisiting this in issue #195.Consider whether
@unchecked Sendableis still needed after the concurrency model change. If the struct is only used within the main actor context, it might be unnecessary.
179-183: Ensure thread safety for state-modifying methodsThe
publishmethod modifies the state of the mock object by updatinglastMessagePublishedName,lastMessagePublishedExtras, andlastMessagePublishedData. With the removal of actor isolation, ensure this method is called in a thread-safe manner.Consider whether this method should be explicitly marked with
@MainActoror if other synchronization mechanisms are needed to prevent race conditions.
96-104:❓ Verification inconclusive
Verify thread safety for async operations
With the removal of actor isolation, the
attach()anddetach()methods might need explicit thread safety measures. If these are intended to be called from arbitrary threads, consider whether they need to be explicitly marked with@MainActoror if synchronization is needed.Also applies to: 110-118
🏁 Script executed:
#!/bin/bash # Check for other @MainActor annotations in similar mock classes rg "@MainActor" --type swift | grep -i "Mock.*\.swift"Length of output: 218
Verify Thread Safety in Async Methods for Mock Realtime Channel
In
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift, both theattach()method (lines 96–104) and its counterpart (lines 110–118) need a careful review regarding thread safety. With the removal of actor isolation, please ensure that these async operations are safe for concurrent access. In particular:
Assess whether these methods may be called from arbitrary threads.
If yes, consider explicitly annotating them with@MainActoror introducing synchronization to guard mutations (e.g., toattachCallCount).Reference from similar mocks:
Other mocks (for example, inExample/AblyChatExample/Mocks/MockSubscriptionStorage.swift) already demonstrate the use of@MainActor, which might indicate the intended design approach.Please re-evaluate the thread-confinement requirements for these methods to prevent potential race conditions.
Sources/AblyChat/DefaultMessages.swift (4)
13-16: Constructor made synchronous as part of MainActor conversionThe constructor has been updated to be synchronous (removing
async), and the initialization ofimplementationno longer usesawait. This is part of the PR's effort to isolate mutable state to the main actor, simplifying the API and improving the developer experience.
48-50: Simplified onDiscontinuity method from async to syncThe
onDiscontinuitymethod has been converted from asynchronous to synchronous by removing theasynckeyword, aligning with the other contributor implementations. This change makes the method signature more consistent with similar methods inDefaultOccupancy,DefaultPresence, and other related classes.
72-72: Made handleChannelEvents call synchronousThe call to
handleChannelEventsis now synchronous, removing the need forawait. This is consistent with the overall approach of simplifying concurrency by moving mutable state to the main actor.
205-207: Made onDiscontinuity implementation synchronousThe
onDiscontinuityimplementation in theImplementationclass is now synchronous, directly delegating to thefeatureChannel.onDiscontinuitymethod. This matches the pattern in other feature components and simplifies the concurrency model.Tests/AblyChatTests/Mocks/MockRoomFactory.swift (2)
3-3: Converted actor to class as part of MainActor migrationThe
MockRoomFactoryhas been changed from anactorto aclassto align with the PR's goal of isolating all mutable state to the main actor instead of using actor-based isolation. This is part of the broader concurrency model simplification.
16-16: Simplified createRoom method to be synchronousThe
createRoommethod signature has been updated to remove theasynckeyword, making it synchronous. This matches the updated protocol definition and simplifies the testing approach by reducing the need forawaitwhen calling this method in tests.Tests/AblyChatTests/DefaultRoomsTests.swift (6)
5-6: Added @mainactor to test structAdding the
@MainActorattribute to the test struct ensures that all methods within this struct execute on the main actor, which is crucial for testing components that have been migrated to use the main actor isolation pattern. This aligns with the PR's objective of isolating mutable state to the main actor.
56-58: Removed await for synchronous methodsThe calls to
rooms.testsOnly_hasRoomMapEntryWithIDand accessingroomFactory.createRoomArgumentsno longer useawaitsince these methods are now synchronous after the main actor migration. This simplifies the test code by eliminating unnecessary async/await patterns.
109-110: Added @sendable to closures in async contextsThe closure passed to
firsthas been marked with@Sendableto ensure it can be safely used across concurrency domains. This is an important safety measure when working with Swift's concurrency model, as it ensures the closure doesn't capture mutable state in an unsafe way.
115-117: Added @sendable to operationWaitEvent closureThe closure processing
operationWaitEventhas been marked with@Sendableto ensure thread safety when working with closures across concurrency domains. This improves the robustness of the test code in the Swift concurrency environment.
123-125: Added @sendable to another operationWaitEvent closureConsistently applied the
@Sendableattribute to all closures used in asynchronous contexts, ensuring thread safety throughout the test code. This systematic approach to marking closures in async contexts demonstrates good practices for working with Swift's concurrency model.
186-186: Added @sendable to closure parameter for async sequenceThe closure for the
roomReleaseCalls.firstcall is now marked with@Sendablefor thread safety, maintaining consistency with other similar closures throughout the test file. This pattern is repeated in multiple places in the test file (lines 235, 295, 402) for all asynchronous sequences.Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
4-4: Converted actor to class for MainActor migrationThe
MockRoomLifecycleManagerhas been changed from anactorto a regularclassas part of the broader migration to use@MainActorfor state isolation. This allows the mock to be more easily used in test contexts that are themselves marked with@MainActor.
54-54: Simplified onRoomStatusChange method to be synchronousThe
onRoomStatusChangemethod no longer has theasynckeyword, making it synchronous to match the updated protocol definition. This aligns with the updated implementation inRoomLifecycleManagerand other related components, creating a more consistent API.Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (2)
4-4: Class declaration changed from actor to classThe change from
final actortofinal classaligns with the PR's objective of isolating mutable state to the main actor rather than using individual actors. This removes the automatic thread safety that actors provide, which is now expected to be handled through@MainActorat the call sites.
17-17: Method made synchronous by removing async keywordThe
onDiscontinuitymethod has been made synchronous by removing theasynckeyword, which aligns with the PR's goal of simplifying the API. Since the implementation simply creates a subscription (a synchronous operation), this change makes sense and matches similar changes in other components likeDefaultMessages,DefaultOccupancy, etc.Tests/AblyChatTests/DefaultRoomReactionsTests.swift (6)
5-5: Added @mainactor attribute to test structThe addition of
@MainActorensures all test methods run on the main actor, which aligns with the PR's strategy of isolating mutable state to the main actor. This change enables the tests to directly interact with the now-synchronous methods without needing explicitawaitkeywords.
16-16: Removed await when instantiating DefaultRoomReactionsThe
awaitkeyword has been removed when instantiatingDefaultRoomReactions, indicating that the constructor is now synchronous. This change is consistent with the PR's goal of simplifying the API by making operations synchronous when possible, while still maintaining proper concurrency control through@MainActor.Also applies to: 42-42, 58-58
28-30: Removed await when accessing channel propertiesThe
awaitkeyword has been removed when accessingchannelproperties, indicating that these properties can now be accessed synchronously. This simplifies the testing code and aligns with the PR's objective of making the SDK's state management more straightforward.
45-45: Removed await when calling subscribe()The
awaitkeyword has been removed when callingdefaultRoomReactions.subscribe(), indicating that this method is now synchronous. This change matches the updated method signature inDefaultRoomReactions, making the API simpler for users.
62-63: Removed await when working with discontinuity eventsThe
awaitkeywords have been removed when callingroomReactions.onDiscontinuity()andfeatureChannel.emitDiscontinuity(), making these operations synchronous. This is part of the overall strategy to simplify the API by removing unnecessary asynchronous operations.
66-66: Added @sendable attribute to closureThe
@Sendableattribute has been added to the closure infirst { @Sendable _ in true }. This is necessary because the closure is used in an asynchronous context (theawait messagesDiscontinuitySubscription.firstcall), ensuring it can be safely used across actor boundaries.Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (8)
5-5: Good use of@MainActorto enforce the new concurrency modelThis annotation ensures all methods within the struct execute on the main actor, aligning with the PR's goal of isolating mutable state to the main actor.
62-63: Good removal of unnecessaryasyncmodifierRemoving the
asyncmodifier from thecreateManagerfunction makes the API simpler and more straightforward, in line with the PR's objective to eliminate unnecessary asynchronous operations.
95-95: Properly switched to synchronous access for subscriptionThe removal of
awaitaligns with the change to maketestsOnly_subscribeToHandledContributorStateChanges()a synchronous method on the main actor.
103-103: Properly switched to synchronous access for subscriptionSimilar to the previous change, removing
awaitreflects the method's transition to synchronous execution on the main actor.
395-396: Good addition of@SendableannotationsAdding
@Sendableannotations to closures passed to async methods is good practice, ensuring proper type-checking for concurrent contexts.Also applies to: 508-509, 1333-1334, 1643-1645, 2060-2061, 2075-2077, 2106-2106
187-187: Successfully migrated all subscription methods to synchronous accessAll subscription method calls have been properly converted from async to synchronous access, maintaining consistent behavior while simplifying the API.
Also applies to: 195-195, 223-223, 246-246, 352-352, 355-355, 438-438, 489-489, 597-597, 620-620, 657-657, 703-703, 747-747, 776-776, 784-784, 822-822, 857-857, 925-925, 1062-1062, 1150-1150, 1221-1221, 1267-1267, 1315-1315, 1318-1318, 1641-1641, 1715-1715, 1811-1811, 1923-1923, 2016-2016, 2019-2019, 2100-2100, 2109-2109
117-117: Successfully migrated all property and method accesses to synchronousAll property accesses and method calls have been properly converted from async to synchronous access, in line with the migration to
@MainActor.Also applies to: 133-133, 232-232, 254-254, 258-258, 283-283, 289-289, 307-307, 367-367, 376-376, 402-408, 450-450, 459-459, 512-514, 530-530, 605-606, 628-628, 632-632, 673-673, 710-710, 713-713, 731-731, 755-756, 805-805, 830-831, 869-869, 872-872, 876-876, 905-905, 908-908, 936-936, 939-939, 943-943, 982-984, 1035-1038, 1071-1073, 1075-1075, 1084-1084, 1164-1164, 1172-1172, 1233-1233, 1275-1275, 1279-1279, 1337-1339, 1341-1343, 1346-1346, 1384-1386, 1414-1414, 1454-1455, 1479-1480, 1521-1521, 1567-1567, 1609-1609, 1661-1661, 1692-1692, 1736-1736, 1739-1739, 1742-1742, 1755-1755, 1770-1770, 1807-1807, 1823-1823, 1859-1859, 1861-1861, 1890-1890, 1905-1906, 1939-1939, 1971-1971, 2041-2043, 2067-2073
1-2211: Overall excellent conversion to@MainActormodelThe changes in this file successfully implement the PR's goal of converting the SDK to use
@MainActorfor state management. The modifications:
- Make the test struct
@MainActor-annotated- Remove all unnecessary
asyncmodifiers from methods- Convert all awaited property accesses and method calls to direct synchronous ones
- Add appropriate
@Sendableannotations for closuresThese changes simplify the code, make it more straightforward, and maintain the same behavior while properly isolating mutable state to the main actor.
Sources/AblyChat/Rooms.swift (6)
6-6: Adopting@MainActoronRooms
Adding@MainActorensures all calls occur on the main actor, simplifying concurrency management for UI-related code.
51-51:nonisolatedproperty forclientOptions
MarkingclientOptionsasnonisolatedmakes it safely accessible from any thread, aligning with your concurrency approach if it's a read-only value.
54-54: Transition fromactortoclass
Switching to a class while adopting@MainActorfor concurrency can simplify design while maintaining isolation, provided all mutable state is still accessed on the main actor.
238-238: Removedawaitinsideget(roomID:options:)
Switching to a synchronous call confirms there is no longer a need for async. Verify that this does not introduce blocking behavior on the main actor.
295-295:createRoomno longerasync
Removing async clarifies that this operation is purely synchronous. Ensure the room creation process doesn’t inadvertently block the main actor for long-running tasks.
347-347: Reworded comment referencing MainActor
The updated comment clarifies that this code runs under@MainActordespite not being anactor. This is consistent with the current concurrency model.Sources/AblyChat/Presence.swift (7)
11-11: Added@MainActortoPresence
Ensures all presence operations execute on the main actor, aligning with the SDK’s overarching concurrency strategy.
81-81: Droppedasyncfromsubscribe(event:bufferingPolicy:)
Returning aSubscription<PresenceEvent>directly simplifies usage. Confirm no async initialization is required behind the scenes.
92-92: Removedasyncfromsubscribe(events:bufferingPolicy:)
Similarly, returning the subscription withoutasyncaligns with the new synchronous approach. Verify that background setup isn’t needed here.
121-121: Synchronous overload ofsubscribe(event:)
Consistent with other subscription methods. Makes usage straightforward in synchronous contexts.
126-126: Synchronous overload ofsubscribe(events:)
Again, removingasyncprovides consistency across the presence API.
130-131: Default single-event subscription
Uses.unboundedas the default buffering policy. This is a logical fallback and mirrors the newly synchronous pattern.
134-135: Default multi-event subscription
Provides.unboundedbuffering by default, matching the single-event subscription approach for consistency.Sources/AblyChat/RoomLifecycleManager.swift (16)
7-14: MainActor isolation added to RoomLifecycleContributor protocol with synchronous emitDiscontinuity method.The protocol is now properly isolated to the MainActor and the
emitDiscontinuitymethod has been changed from async to synchronous, which aligns with the PR objective of isolating mutable state to the MainActor.
17-23: MainActor isolation added to RoomLifecycleManager with synchronous room status access.The protocol now properly isolates its functionality to the MainActor and provides synchronous access to the room status and status change subscription. This change aligns with the PR objective of simplifying the API by removing unnecessary async operations for state access.
27-35: MainActor isolation added to RoomLifecycleManagerFactory with synchronous factory method.The factory protocol is now properly isolated to the MainActor and the
createManagermethod has been made synchronous, allowing for simpler object creation without awaiting. This is consistent with the overall approach of isolating mutable state to the MainActor.
53-53: Converted DefaultRoomLifecycleManager from actor to MainActor-isolated class.The class has been changed from an
actorto a regularclass, which is consistent with the isolation approach using@MainActor. This change simplifies the concurrency model by centralizing isolation to the MainActor instead of using separate actor instances.
71-84: Synchronous convenience initializer for DefaultRoomLifecycleManager.The initializer has been made synchronous by removing the
asynckeyword, which aligns with the MainActor isolation pattern. Since all initialization work is now performed on the MainActor, asynchronous initialization is no longer needed.
87-103: Synchronous test-specific convenience initializer for DefaultRoomLifecycleManager.Similar to the regular convenience initializer, the test-specific initializer has been made synchronous by removing the
asynckeyword, maintaining consistency with the MainActor isolation pattern.
124-124: Synchronous subscription to channel state.The channel state subscription is now performed synchronously, which is possible because the code is executing on the MainActor. This simplifies the initialization code by removing the need to await subscriptions.
410-410: Synchronous discontinuity event emission.The
emitDiscontinuitymethod call is now synchronous, aligning with the updated protocol declaration. This simplifies the code by removing unnecessary asynchronous operations for event emission.
425-426: Using async sequence filtering directly in conditional check.This code uses
contributors.async.mapwith anallSatisfyoperation directly in the conditional check. This is a more elegant way to handle async iteration while still usingawaitappropriately for the asynchronous operation.
463-464: Clarified MainActor execution order in comments.The comment explains how tasks created within MainActor-isolated code will execute in a predictable order, providing important context for why the
.suspendedAwaitingStartOfRetryOperationand.suspendedstatuses will occur in the expected sequence.
773-775: Clarified MainActor execution order in comments for retry operation.Similar to the previous comment, this explains the expected execution order for task creation within MainActor-isolated code, providing important context about the status transition sequence.
787-788: Clarified MainActor execution order in comments for rundown operation.Another explanatory comment about task execution order within MainActor-isolated code, providing consistent documentation about status transition sequences throughout the codebase.
812-816: Converted emitPendingDiscontinuityEvents to synchronous method.The method has been made synchronous, which is appropriate since it now runs on the MainActor and calls the synchronous
emitDiscontinuitymethod. This is consistent with the overall approach of making state operations synchronous when isolated to the MainActor.
822-822: Synchronous discontinuity event emission in loop.Similar to line 410, this call to
emitDiscontinuityis now synchronous, aligning with the updated protocol declaration and simplifying the code.
1203-1203: Simplified async sequence consumption.The code now uses a more concise pattern for awaiting the first element of an async sequence, using the
firstmethod with a closure that always returnstrue. This improves readability while maintaining the same functionality.
626-627: Clarified understanding of MainActor isolation in comments.The comment explains the author's understanding of how MainActor isolation affects method suspension and continuation handling, which provides valuable context for understanding the code's concurrency behavior. This kind of documentation is essential when working with Swift's concurrency model.
Sources/AblyChat/Room.swift (20)
6-13: MainActor isolation added to Room protocol with nonisolated roomID property.The
Roomprotocol is now properly isolated to the MainActor, and theroomIDproperty is marked asnonisolatedto allow access from any context without requiring MainActor isolation. This is appropriate since the room ID is immutable and can be safely accessed from any thread.
20-20: Marked messages property as nonisolated.The
messagesproperty is now marked asnonisolated, allowing it to be accessed from any context without MainActor isolation. This is appropriate since the property returns an instance conforming to theMessagesprotocol, which should handle its own thread-safety concerns.
29-29: Marked presence property as nonisolated.The
presenceproperty is now marked asnonisolated, allowing it to be accessed from any context without MainActor isolation. This improves API usability while delegating thread-safety concerns to thePresenceprotocol implementation.
38-38: Marked reactions property as nonisolated.The
reactionsproperty is now marked asnonisolated, allowing it to be accessed from any context. This is consistent with the treatment of other feature-specific properties in the protocol.
47-47: Marked typing property as nonisolated.The
typingproperty is now marked asnonisolated, allowing it to be accessed from any context. This provides a more convenient API for clients while pushing thread-safety concerns to the implementation.
56-56: Marked occupancy property as nonisolated.The
occupancyproperty is now marked asnonisolated, consistent with the other feature-specific properties in the protocol. This approach simplifies API usage by allowing direct access without MainActor switching.
63-63: Converted status property to synchronous access.The
statusproperty now provides synchronous access to the room status without requiringawait. This aligns with the PR objective of simplifying the API and improving usability while maintaining thread safety through MainActor isolation.
73-73: Converted onStatusChange method to synchronous.The
onStatusChangemethod now returns a subscription synchronously without requiringawait. This provides a more straightforward API for subscribing to status changes while maintaining thread safety through MainActor isolation.
78-78: Converted default onStatusChange implementation to synchronous.The default implementation of
onStatusChange()is now synchronous, consistent with the protocol declaration. This ensures that the default implementation matches the updated protocol signature.
106-106: Marked options property as nonisolated.The
optionsproperty is now marked asnonisolated, allowing it to be accessed from any context. This is appropriate since room options are immutable after room creation and can be safely accessed from any thread.
110-112: Updated default onStatusChange implementation.The default implementation has been updated to be synchronous, directly calling the synchronous
onStatusChange(bufferingPolicy:)method without awaiting. This maintains consistency with the updated protocol declarations.
140-144: MainActor isolation added to RoomFactory with synchronous createRoom method.The
RoomFactoryprotocol is now properly isolated to the MainActor and thecreateRoommethod has been made synchronous. This simplifies room creation while maintaining thread safety through MainActor isolation.
150-159: Converted createRoom implementation to synchronous.The implementation of
createRoominDefaultRoomFactoryis now synchronous, directly calling the synchronousDefaultRoominitializer without awaiting. This is consistent with the updated protocol declaration and improves code simplicity.
162-162: Converted DefaultRoom from actor to MainActor-isolated class.The
DefaultRoomclass has been changed from anactorto a regularclass. Since the class is used in a context that's isolated to the MainActor, this change simplifies the concurrency model while maintaining thread safety.
226-226: Converted DefaultRoom initializer to synchronous.The initializer has been made synchronous by removing the
asynckeyword. This is appropriate since all initialization work is now performed on the MainActor, eliminating the need for asynchronous initialization.
243-246: Synchronous lifecycle manager creation.The code now creates the lifecycle manager synchronously, which is possible because the factory method is now synchronous when isolated to the MainActor. This improves code readability by removing unnecessary awaiting.
250-256: Synchronous DefaultMessages initialization.The
DefaultMessagesinstance is now created synchronously, which is appropriate since its initializer no longer needs to be asynchronous when isolated to the MainActor. This is consistent with the overall approach of simplifying the API through MainActor isolation.
259-264: Synchronous DefaultRoomReactions initialization.Similar to
DefaultMessages, theDefaultRoomReactionsinstance is now created synchronously. This maintains consistency across all feature implementations.
429-430: Converted onStatusChange implementation to synchronous.The implementation of
onStatusChangeinDefaultRoomis now synchronous, directly calling the synchronousonRoomStatusChangemethod on the lifecycle manager without awaiting. This is consistent with the updated protocol declaration.
433-435: Converted status property implementation to synchronous.The
statusproperty implementation inDefaultRoomis now synchronous, directly accessing the synchronousroomStatusproperty of the lifecycle manager without awaiting. This improves API usability while maintaining thread safety through MainActor isolation.
maratal
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
SwiftUI views are automatically @mainactor.
It doesn't copy the @_inheritActorContext attribute that the underlying SwiftUI `task` modifier uses, meaning that when we pass it a closure whose isolation matters (which I will do in #232 when we make the SDK @mainactor) it doesn't behave as we'd wish.
In 7d6acde we made the decision to use `actor` types to manage the mutable state of the SDK. Whilst we've been able to build the SDK using this approach, it has proved cumbersome. The public API is littered with `async` methods and getters for things that really should be synchronous, such as adding a subscription or fetching a room's state. This deviates from the JavaScript API and makes call sites look complicated. The use of `async` for fetching room state also means that it's impossible for a user to, for example, check a room's state and then reason about what operations they should be able to attempt to perform on the room (i.e. without the SDK immediately rejecting the attempt), because the room state can change under their feet. This is one of a class of threading-related problems that I describe in #260. Internally, we find ourselves having to write `await` to do basic things like fetching the room lifecycle manager's state or to find out the arguments with which a mock method was called. And similarly, one internal component can't rely on the state that's isolated to a different internal isolation domain in order to make a decision, because this state might change under its feet. To address this, here I have decided to isolate all of the SDK's mutable state to the main actor. This simplifies the experience of developing the SDK, and it simplifies the experience of interacting with it from the main thread, which is where most of our users interact with it from. (Note that this does not prevent us from using background tasks internally for moving heavy processing off the main thread; I'm specifically talking about _state_.) This also moves us closer to being able to address #49 (keeping our internal state in sync with that of the underlying ably-cocoa objects). Note that this change does decrease the parallelisability of our test suite. But it still runs very fast. Have removed: - all `actor` types (both in SDK and mocks) - nearly all locking (there are a couple of non-MainActor types for which we still have it) There might be some simplifications that can be made for the lifecycle manager tests now, but I'm not going to look at them now because that class will change a lot for the single-channel work in #242. I'll update the ably.com documentation in #254. Even after this change, Messages.subscribe is still `async` because its implementation requires it to be. I believe that this is a bug; have created #257 for it. Resolves #232.
6cf993e to
fc83fc1
Compare
There was a problem hiding this 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
♻️ Duplicate comments (1)
Sources/AblyChat/DefaultConnection.swift (1)
84-84: 🛠️ Refactor suggestionConsider making the termination handler itself @mainactor.
Instead of wrapping the code in a Task with the @mainactor attribute, consider making the entire closure @mainactor:
-subscription.addTerminationHandler { [weak self] in - Task { @MainActor in - self?.realtime.connection.off(eventListener) - } +subscription.addTerminationHandler { @MainActor [weak self] in + self?.realtime.connection.off(eventListener) }This would be more consistent with the approach of isolating operations to the main actor and eliminate the need for the Task wrapper.
🧹 Nitpick comments (1)
Sources/AblyChat/SubscriptionStorage.swift (1)
44-45: Consider pruning nil references while emitting.
Currently, weak subscription references that have becomenilremain in the dictionary. You might optionally remove them to keep it uncluttered.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)
📒 Files selected for processing (54)
CONTRIBUTING.md(2 hunks)Example/AblyChatExample/ContentView.swift(9 hunks)Example/AblyChatExample/Mocks/MockClients.swift(11 hunks)Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift(1 hunks)README.md(1 hunks)Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift(6 hunks)Sources/AblyChat/ChatAPI.swift(1 hunks)Sources/AblyChat/ChatClient.swift(4 hunks)Sources/AblyChat/Connection.swift(1 hunks)Sources/AblyChat/DefaultConnection.swift(2 hunks)Sources/AblyChat/DefaultMessages.swift(6 hunks)Sources/AblyChat/DefaultOccupancy.swift(4 hunks)Sources/AblyChat/DefaultPresence.swift(3 hunks)Sources/AblyChat/DefaultRoomLifecycleContributor.swift(1 hunks)Sources/AblyChat/DefaultRoomReactions.swift(2 hunks)Sources/AblyChat/DefaultTyping.swift(10 hunks)Sources/AblyChat/EmitsDiscontinuities.swift(2 hunks)Sources/AblyChat/Messages.swift(3 hunks)Sources/AblyChat/Occupancy.swift(3 hunks)Sources/AblyChat/Presence.swift(4 hunks)Sources/AblyChat/Room.swift(14 hunks)Sources/AblyChat/RoomFeature.swift(2 hunks)Sources/AblyChat/RoomLifecycleManager.swift(15 hunks)Sources/AblyChat/RoomReactions.swift(3 hunks)Sources/AblyChat/Rooms.swift(6 hunks)Sources/AblyChat/SimpleClock.swift(1 hunks)Sources/AblyChat/Subscription.swift(2 hunks)Sources/AblyChat/SubscriptionStorage.swift(1 hunks)Sources/AblyChat/TimerManager.swift(1 hunks)Sources/AblyChat/Typing.swift(3 hunks)Tests/AblyChatTests/ChatAPITests.swift(1 hunks)Tests/AblyChatTests/DefaultChatClientTests.swift(1 hunks)Tests/AblyChatTests/DefaultMessagesTests.swift(7 hunks)Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift(100 hunks)Tests/AblyChatTests/DefaultRoomOccupancyTests.swift(3 hunks)Tests/AblyChatTests/DefaultRoomReactionsTests.swift(5 hunks)Tests/AblyChatTests/DefaultRoomTests.swift(15 hunks)Tests/AblyChatTests/DefaultRoomsTests.swift(12 hunks)Tests/AblyChatTests/IntegrationTests.swift(14 hunks)Tests/AblyChatTests/Mocks/MockChannels.swift(2 hunks)Tests/AblyChatTests/Mocks/MockConnection.swift(2 hunks)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift(2 hunks)Tests/AblyChatTests/Mocks/MockInternalRealtimeClientFactory.swift(1 hunks)Tests/AblyChatTests/Mocks/MockRealtime.swift(3 hunks)Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift(3 hunks)Tests/AblyChatTests/Mocks/MockRealtimePresence.swift(1 hunks)Tests/AblyChatTests/Mocks/MockRoom.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomFactory.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift(2 hunks)Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift(1 hunks)Tests/AblyChatTests/Mocks/MockSimpleClock.swift(1 hunks)Tests/AblyChatTests/SubscriptionStorageTests.swift(3 hunks)Tests/AblyChatTests/SubscriptionTests.swift(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (35)
- Tests/AblyChatTests/DefaultChatClientTests.swift
- Tests/AblyChatTests/DefaultMessagesTests.swift
- Tests/AblyChatTests/ChatAPITests.swift
- Tests/AblyChatTests/SubscriptionStorageTests.swift
- Tests/AblyChatTests/DefaultRoomTests.swift
- Sources/AblyChat/DefaultRoomLifecycleContributor.swift
- README.md
- Sources/AblyChat/SimpleClock.swift
- Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift
- Tests/AblyChatTests/IntegrationTests.swift
- Tests/AblyChatTests/Mocks/MockInternalRealtimeClientFactory.swift
- Sources/AblyChat/TimerManager.swift
- Example/AblyChatExample/Mocks/MockSubscriptionStorage.swift
- Sources/AblyChat/ChatAPI.swift
- Sources/AblyChat/Connection.swift
- Sources/AblyChat/EmitsDiscontinuities.swift
- Sources/AblyChat/Subscription.swift
- Tests/AblyChatTests/SubscriptionTests.swift
- Sources/AblyChat/Occupancy.swift
- Sources/AblyChat/Typing.swift
- Tests/AblyChatTests/DefaultRoomOccupancyTests.swift
- Sources/AblyChat/RoomReactions.swift
- Sources/AblyChat/DefaultRoomReactions.swift
- Tests/AblyChatTests/Mocks/MockSimpleClock.swift
- Sources/AblyChat/DefaultTyping.swift
- Sources/AblyChat/RoomFeature.swift
- Sources/AblyChat/DefaultPresence.swift
- Sources/AblyChat/ChatClient.swift
- Sources/AblyChat/DefaultMessages.swift
- Sources/AblyChat/Rooms.swift
- Sources/AblyChat/DefaultOccupancy.swift
- Example/AblyChatExample/Mocks/MockClients.swift
- Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift
- Example/AblyChatExample/ContentView.swift
- Sources/AblyChat/Room.swift
🧰 Additional context used
🧬 Code Definitions (9)
Tests/AblyChatTests/DefaultRoomReactionsTests.swift (6)
Sources/AblyChat/DefaultMessages.swift (4)
subscribe(28-30)subscribe(76-171)onDiscontinuity(48-50)onDiscontinuity(207-209)Sources/AblyChat/DefaultRoomReactions.swift (4)
subscribe(26-28)subscribe(69-121)onDiscontinuity(30-32)onDiscontinuity(124-126)Sources/AblyChat/RoomReactions.swift (1)
subscribe(45-47)Sources/AblyChat/EmitsDiscontinuities.swift (1)
onDiscontinuity(34-36)Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
emitDiscontinuity(15-17)Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (1)
emitDiscontinuity(21-23)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
Sources/AblyChat/RoomLifecycleManager.swift (1)
onRoomStatusChange(297-299)
Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (7)
Sources/AblyChat/DefaultOccupancy.swift (2)
onDiscontinuity(24-26)onDiscontinuity(91-93)Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
onDiscontinuity(19-21)Sources/AblyChat/DefaultMessages.swift (2)
onDiscontinuity(48-50)onDiscontinuity(207-209)Sources/AblyChat/DefaultRoomReactions.swift (2)
onDiscontinuity(30-32)onDiscontinuity(124-126)Sources/AblyChat/DefaultTyping.swift (2)
onDiscontinuity(32-34)onDiscontinuity(211-213)Sources/AblyChat/RoomFeature.swift (1)
onDiscontinuity(69-71)Tests/AblyChatTests/DefaultMessagesTests.swift (1)
onDiscontinuity(156-173)
Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (1)
createManager(41-50)Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (1)
createManager(56-71)
Tests/AblyChatTests/DefaultRoomsTests.swift (6)
Sources/AblyChat/Rooms.swift (3)
testsOnly_hasRoomMapEntryWithID(303-313)release(316-354)testsOnly_subscribeToOperationWaitEvents(145-147)Sources/AblyChat/Room.swift (1)
release(418-425)Tests/AblyChatTests/DefaultRoomTests.swift (1)
release(268-290)Tests/AblyChatTests/Mocks/MockRoom.swift (1)
release(55-62)Sources/AblyChat/RoomLifecycleManager.swift (1)
testsOnly_subscribeToOperationWaitEvents(581-583)Tests/AblyChatTests/Mocks/MockRoomFactory.swift (1)
setRoom(12-14)
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (2)
Sources/AblyChat/RoomLifecycleManager.swift (4)
testsOnly_subscribeToHandledContributorStateChanges(350-352)testsOnly_subscribeToHandledTransientDisconnectTimeouts(378-380)onRoomStatusChange(297-299)testsOnly_subscribeToOperationWaitEvents(581-583)Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (1)
onRoomStatusChange(54-56)
Sources/AblyChat/Presence.swift (1)
Sources/AblyChat/DefaultPresence.swift (4)
subscribe(52-54)subscribe(56-58)subscribe(261-282)subscribe(284-311)
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (3)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (10)
subscribe(243-245)subscribe(352-354)subscribe(356-358)unsubscribe(235-237)unsubscribe(360-362)on(227-229)on(231-233)on(396-398)off(247-249)off(400-402)Tests/AblyChatTests/Mocks/MockRealtimePresence.swift (2)
subscribe(5-7)unsubscribe(13-15)Tests/AblyChatTests/Mocks/MockConnection.swift (2)
on(14-16)off(18-20)
Sources/AblyChat/RoomLifecycleManager.swift (7)
Sources/AblyChat/DefaultRoomLifecycleContributor.swift (1)
emitDiscontinuity(15-17)Tests/AblyChatTests/Mocks/MockRoomLifecycleContributor.swift (1)
emitDiscontinuity(19-21)Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
onRoomStatusChange(54-56)waitToBeAbleToPerformPresenceOperations(62-64)Sources/AblyChat/RoomFeature.swift (1)
waitToBeAbleToPerformPresenceOperations(73-75)Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift (1)
createManager(11-14)Tests/AblyChatTests/DefaultRoomTests.swift (1)
status(294-312)Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)
subscribeToState(193-204)
⏰ Context from checks skipped due to timeout of 90000ms (5)
- GitHub Check: Example app, tvOS (Xcode 16.2)
- GitHub Check: Generate code coverage (Xcode 16.2)
- GitHub Check: Xcode, tvOS (Xcode 16.2)
- GitHub Check: Xcode, iOS (Xcode 16.2)
- GitHub Check: Xcode, macOS (Xcode 16.2)
🔇 Additional comments (69)
Tests/AblyChatTests/Mocks/MockRoomFactory.swift (2)
3-3: Correct actor to class conversion.The change from
actortoclassaligns with the PR's objective of moving mutable state management to the main actor instead of using distributed actors. This simplifies the concurrency model as part of the broader refactoring strategy.
16-16: Appropriate removal of async keyword.Removing the
asynckeyword from the method signature matches the changes in theRoomFactoryprotocol and reflects the project's goal to simplify the API by making operations synchronous where possible. This change also maintains consistency with the refactored concurrency model across the codebase.Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (3)
4-4: Conversion from actor to class aligns with PR objectivesThis change is part of the broader refactoring to isolate mutable state to the main actor rather than using multiple actors. Since this is a mock class used in tests, ensure all test code that uses this mock has been properly updated to work with the new concurrency model.
17-17: Method signature updated to remove async keywordThis change aligns with the PR objective of simplifying the API by removing unnecessary asynchronous patterns. The method now returns a
Subscription<DiscontinuityEvent>synchronously, which is consistent with similar changes in other components likeDefaultMessages,DefaultOccupancy, etc.
4-36: Verify thread safety with actor-to-class conversionWith the removal of the actor-based protection, ensure that this mock class is only accessed from the main thread in tests or is otherwise protected from concurrent access if used from multiple threads. Consider adding a comment documenting this requirement for future maintainers.
Tests/AblyChatTests/Mocks/MockChannels.swift (4)
4-4: Approved: Removal of@unchecked Sendablealigns with the PR objectives.This change correctly removes the
@unchecked Sendablemarker from the class declaration, which aligns with the PR's goal of moving away from actor types and isolated state management toward the@MainActorpattern. This simplifies the concurrency model by relying on the main actor for thread safety instead of manual sendability checks.
6-7: Simplified property access by removing underscore prefix and mutex.The properties have been renamed from
_getArgumentsand_releaseArgumentstogetArgumentsandreleaseArguments, removing the underscore prefix while maintaining theprivate(set)access level. This change, coupled with the removal of a mutex lock (not visible in the diff but mentioned in the summary), indicates that these properties will now be safely accessed through the main actor, eliminating the need for explicit synchronization.
14-14: Removed mutex-based synchronization for direct property access.The method now directly appends to the
getArgumentsarray without mutex protection. This is safe because the class will be accessed through the@MainActor, ensuring all interactions happen on the main thread, thus eliminating the need for explicit synchronization.
24-24: Removed mutex-based synchronization for direct property access.Similar to the
getmethod, this implementation now directly appends to thereleaseArgumentsarray without mutex protection. This change simplifies the code and is safe within the context of the@MainActorpattern being implemented across the SDK.Sources/AblyChat/DefaultConnection.swift (5)
8-10: Good conversion to synchronous properties.The change from an actor-based approach to direct properties with
internal private(set)properly isolates the mutable state to the main thread while maintaining encapsulation.
15-16: Clean initialization of connection properties.The direct initialization of
statusanderrorproperties simplifies the code by removing the need for asynchronous calls during initialization.
41-41: Successful conversion to synchronous timer check.The removal of
awaitfromtimerManager.hasRunningTask()aligns with the goal of making state operations synchronous.
45-70: Well-implemented transient disconnect handling.The transient disconnect timer logic has been properly updated to use synchronous property updates. The conditions and state transitions follow the requirements properly.
79-80: Effective direct state updates.Direct updates to the status and error properties remove the dependency on an actor and properly handle state transitions.
CONTRIBUTING.md (1)
42-42: Highlighting main actor isolation is a great clarity boost.
This new guideline explicitly stating that the SDK’s mutable state should be isolated to the main actor aligns well with Swift’s concurrency philosophy, helping contributors write safer, more consistent code.Sources/AblyChat/SubscriptionStorage.swift (5)
6-7: Adopting@MainActoris a solid concurrency safety measure.
Marking the class with@MainActorremoves the manual locking overhead, ensuring all mutable state is consistently updated on the main actor.
21-21: Storing subscriptions in the dictionary on the main actor.
Directly adding the subscription to the dictionary is safe under main actor isolation.
24-26: Terminating subscription withTask { @MainActor in ... }ensures concurrency correctness.
Scheduling the termination handler on the main actor is consistent with the class’s concurrency model.
34-34: Returningsubscriptions.countunder @mainactor is correct.
This approach uses main actor isolation instead of locks, which is succinct and safe.
39-39: Removing subscription from the dictionary under main actor is clean.
Eliminating locks and relying on the main actor is straightforward and avoids race conditions.Sources/AblyChat/Presence.swift (6)
11-11: Marking the protocol with@MainActorboosts safety.
This is consistent with the SDK’s practice of isolating mutable state on the main actor.
130-131: Default implementation referencing the new synchronous subscription.
Straightforward call to the unbounded version; no concerns.
134-135: Consistent synchronous approach for multiple-event subscription.
Similarly references the unbounded overload; looks good.
126-126:❓ Verification inconclusive
Confirm multiple-event subscription no longer requiring
await.
Likewise, verify no references are expectingasyncusage here.
🏁 Script executed:
#!/bin/bash rg -A3 "await\s+.*subscribe\(events:\)"Length of output: 41
Action: Verify that all subscription calls are fully synchronous and no callers use
awaitonsubscribe(events:).The refactored method in
Sources/AblyChat/Presence.swift(line 126) now returns a synchronousSubscription<PresenceEvent>, and our initial search forawait subscribe(events:)produced no output. This suggests that no call sites are mistakenly expecting anasynccall. However, please manually review any code that calls this API (and associated tests) to confirm that there are no lingering assumptions requiring asynchronous usage.
121-121:✅ Verification successful
Transition from
async -> Subscriptionto synchronous.
Make sure no external code was relying on anawaitcall for single-event subscription.
🏁 Script executed:
#!/bin/bash rg -A3 "await\s+.*subscribe\(event:\)"Length of output: 40
I'm not fully confident with the initial search result. To be sure no external code is mistakenly relying on asynchronous usage of the subscribe call, please run an additional, slightly modified verification:
🏁 Script executed:
#!/bin/bash # Additional check: Searching for any asynchronous usage of subscribe calls with "await". rg -A3 "await\s+subscribe\s*\("Length of output: 227
Review Confirmation: Transition Verified
After verifying the asynchronous usage of the
subscribemethod, we found no instances ofawait subscribe(event:)anywhere in the repository. Although an asynchronous call (try await subscribe(bufferingPolicy: .unbounded)) exists inSources/AblyChat/Messages.swift, it pertains to a different subscription method. Therefore, the synchronous change forPresence.subscribe(event:)is isolated and safe.
81-81:✅ Verification successful
Confirm no external calls rely on the old
asyncsignature.
Changing this to a synchronous method might break code expectingawait.Run this search to verify there are no references depending on
asynccalls tosubscribe(event:bufferingPolicy:):
🏁 Script executed:
#!/bin/bash rg -A3 "await\s+.*subscribe\(event:"Length of output: 38
I'll run an additional couple of searches to confirm there are no external calls using an
awaitwith this method. In particular, I want to check both for anyawait subscribe(event:usage and list all occurrences ofsubscribe(event:to inspect their context.Please run these additional searches and review the results. If they confirm that no external call is using
await subscribe(event:, you can safely proceed with converting the method to synchronous.
🏁 Script executed:
#!/bin/bash # 1. Search for any asynchronous calls (using 'await') on subscribe(event:) rg -n -A3 "await\s+subscribe\(event:" --color always # 2. List all occurrences of subscribe(event:) to verify how it's used rg -n -A3 "subscribe\(event:" --color alwaysLength of output: 3558
Synchronous Signature Change Confirmed
The additional search results show no usage of anawaiton calls tosubscribe(event:. All detected usages are synchronous, so converting the method signature from an async form to a synchronous one should not impact any external callers.Sources/AblyChat/Messages.swift (3)
9-9: Properly isolates theMessagesprotocol to the main actor.This change ensures that all implementations of the
Messagesprotocol are executed on the main actor, aligning with the SDK's new guideline of isolating all mutable state to the main actor.
86-86: Correctly markschannelproperty asnonisolated.The
nonisolatedkeyword allows this property to be accessed from any actor without requiring the caller to be on the main actor. This is appropriate for a property that provides access to the underlying channel, which may need to be accessed from various concurrency contexts.
308-309: Ensures termination handlers are executed on the main actor.Adding the
@MainActorattribute to theaddTerminationHandlermethod ensures that termination handlers are properly executed on the main actor, which is critical for UI updates and state management.Sources/AblyChat/RoomLifecycleManager.swift (9)
7-8: Properly isolates theRoomLifecycleContributorprotocol to the main actor.The
@MainActorannotation ensures that all implementations will be executed on the main actor, and makingemitDiscontinuitysynchronous simplifies the interaction model. This aligns with the relevant implementations inDefaultRoomLifecycleContributor.swift.Also applies to: 14-14
17-18: IsolatesRoomLifecycleManagerprotocol and removes unnecessary async.Making the protocol
@MainActorannotated and removingasyncfrom theroomStatusproperty andonRoomStatusChangemethod simplifies the API while maintaining thread safety through main actor isolation.Also applies to: 22-23
27-28: Isolates theRoomLifecycleManagerFactoryprotocol to the main actor.Making the protocol
@MainActorannotated and removingasyncfrom thecreateManagermethod return type ensures that factory operations happen on the main actor, maintaining a consistent concurrency model.Also applies to: 35-35
53-53: ConvertsDefaultRoomLifecycleManagerfrom an actor to a class.This change is significant as it shifts from Swift's actor isolation model to explicit main actor isolation. This aligns with the PR's goal to isolate all mutable state to the main actor rather than using distributed actor types.
124-124: Removes await for channel subscriptions under main actor.Now that the class is main actor isolated, the channel subscriptions can be done synchronously, simplifying the code while maintaining thread safety guarantees.
626-627: Updates comment to accurately reflect main actor concurrency model.The comment has been updated to explain the behavior of MainActor-isolated calls within the same actor, which is important for understanding the execution flow in this new concurrency model.
816-816: MakesemitPendingDiscontinuityEventssynchronous.With the shift to main actor isolation, this method no longer needs to be asynchronous, simplifying the control flow while maintaining thread safety.
822-822: MakesemitDiscontinuitycall synchronous.Now that the
emitDiscontinuitymethod on the contributor is synchronous and main actor isolated, the call no longer needs to useawait.
1127-1127: Simplifies channel state subscription iteration.Removes the
awaitkeyword since the context is now main actor isolated, simplifying the iteration over state changes.Tests/AblyChatTests/Mocks/MockConnection.swift (2)
4-4: Removes unnecessaryNSObjectinheritance.Simplifies the mock class by removing inheritance from
NSObjectwhile maintaining the essentialInternalConnectionProtocolconformance.
14-14: Updates callback to be executed on the main actor.Adding
@MainActorto the closure parameter ensures that any state changes from connection events are processed on the main actor, maintaining thread safety.Tests/AblyChatTests/DefaultRoomsTests.swift (5)
5-5: Properly isolates test struct to the main actor.Adding
@MainActorto the test struct ensures that all test methods run on the main actor, which is necessary for interacting with the now main actor-isolated SDK components.
56-58: Simplifies property and method access with main actor isolation.With the test struct now marked as
@MainActor, these calls no longer need to useawaitsince they're executed on the same actor as the subject under test.
109-110: Correctly marks closures as@Sendablein async contexts.Adding the
@Sendableattribute to closures used in asynchronous contexts ensures they can be safely used across different execution contexts, which is required by Swift's concurrency model.Also applies to: 115-117, 123-125, 186-187, 192-194, 235-236, 242-244, 295-296, 302-304, 338-339, 344-346, 402-403
111-111: Makes subscription calls synchronous with main actor isolation.These calls to obtain subscriptions can now be made synchronously since they're executed on the same actor as the subject under test.
Also applies to: 188-188, 238-238, 298-298, 339-339
313-313: Simplifies test assertions with main actor isolation.With the test struct now marked as
@MainActor, these assertions no longer need to useawaitsince they're executed on the same actor as the subject under test.Also applies to: 369-369, 405-405
Tests/AblyChatTests/Mocks/MockRoomLifecycleManagerFactory.swift (2)
3-3: Clean conversion from actor to classThis change aligns with the PR objective of removing actors and simplifying the concurrency model. The factory no longer needs to be an actor since its state can be safely managed on the main thread.
11-11: Correct removal of async modifierThe
createManagermethod signature has been properly updated to remove theasynckeyword, making it synchronous. This matches the implementation in the realRoomLifecycleManagerFactoryand simplifies the API by reducing unnecessary asynchronous calls.Tests/AblyChatTests/Mocks/MockRealtimePresence.swift (2)
5-5: Improved thread safety with @mainactorThe callback type has been updated to explicitly use
@MainActor, ensuring that presence subscription callbacks will be executed on the main thread. This is a positive change that improves thread safety when handling presence events.
9-9: Improved thread safety with @mainactorSimilarly, this action-specific subscribe method now properly ensures that callbacks run on the main thread with the
@MainActorattribute, maintaining consistency with the other subscribe method.Tests/AblyChatTests/Mocks/MockRoom.swift (2)
4-4: Clean conversion from actor to classThis change aligns with the PR objective of simplifying the concurrency model by isolating mutable state to the main actor instead of using actor-based isolation.
43-43: Appropriate removal of async modifierThe
onStatusChangemethod has been correctly updated to be synchronous by removing theasynckeyword. This simplifies the API while still returning a subscription that can be used to observe status changes asynchronously.Tests/AblyChatTests/Mocks/MockRoomLifecycleManager.swift (2)
4-4: Clean conversion from actor to classThis change aligns with the PR objective of removing actors and simplifying the concurrency model by isolating mutable state to the main actor.
54-54: Appropriate removal of async modifierThe
onRoomStatusChangemethod has been correctly updated to be synchronous by removing theasynckeyword. This matches the implementation in the realRoomLifecycleManagerand simplifies the subscription API while maintaining appropriate asynchronous operations for the actual attachment, detachment, and release methods.Tests/AblyChatTests/DefaultRoomReactionsTests.swift (2)
5-5: Appropriate use of @mainactor for test classAdding @mainactor to the test struct ensures all test methods run on the main actor, which aligns with the PR's objective of isolating mutable state to the main actor. This change is consistent with the broader refactoring approach.
15-16: Correctly removed await keywords from synchronous operationsGood job removing await keywords from method calls like initialization, property accesses, and method calls that are now synchronous due to the @mainactor isolation. This simplifies the test code and makes it more readable while maintaining the same functionality.
Also applies to: 28-30, 45-45, 62-63, 66-66
Tests/AblyChatTests/Mocks/MockRealtime.swift (4)
6-6: Appropriate conversion from actor to classConverting MockRealtime from an actor to a regular class aligns with the PR's goal of isolating mutable state to the main actor rather than using actor-based concurrency.
12-13: Simplified property access without mutex protectionGood refactoring of the properties to use direct access rather than mutex-protected private backing properties. This simplifies the code while maintaining thread safety through the main actor.
31-33: Removed explicit synchronization in request methodAppropriately removed mutex locking when appending to the requestArguments array, relying instead on the main actor for thread safety. This reduces code complexity while maintaining correctness.
47-49: Removed explicit synchronization in createWrapperSDKProxy methodDirect assignment to createWrapperSDKProxyOptionsArgument without mutex protection simplifies the code, relying on the main actor for thread safety instead.
Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift (5)
5-5: Appropriate use of @mainactor for test classAdding @mainactor to the test struct ensures all test methods run on the main actor, which aligns with the PR's objective of isolating mutable state to the main actor. This is consistent with the broader refactoring approach.
62-63: Simplified method signature by removing asyncGood job refactoring the createManager method to be synchronous. Since it's now running on the main actor and doesn't contain any asynchronous work, this simplifies the API.
95-96: Correctly removed await from synchronous subscriptionsAppropriately removed await keywords from subscription methods that are now synchronous due to the @mainactor isolation. This simplifies the test code while maintaining functionality.
Also applies to: 103-104
117-117: Correctly removed await from property accessGood job removing await keywords from property accesses throughout the file. This simplifies the code and makes it more readable while maintaining the same functionality. Properties accessed on the main actor no longer require await.
Also applies to: 134-134, 232-232, 258-258, 307-307, 367-367, 402-408, 450-450, 512-514, 530-530, 605-606, 632-632, 755-756, 831-831, 875-876, 943-943, 1075-1076, 1165-1165, 1279-1280, 1346-1346
395-396: Added @sendable to closures in async contextsCorrectly added @sendable to closures used in async contexts, ensuring proper concurrency safety. This is important for maintaining correctness when working with async sequences.
Also applies to: 410-412, 507-508, 643-645, 1333-1334, 2060-2062
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (5)
4-4: Appropriate conversion from actor to classConverting MockRealtimeChannel from an actor to a regular class aligns with the PR's goal of isolating mutable state to the main actor rather than using actor-based concurrency. This change is consistent with the broader refactoring approach.
142-142: Correctly updated subscribe method to use @mainactor for callbackGood job updating the subscribe method to specify that the callback will be executed on the main actor. This ensures thread safety for UI-related code while maintaining compatibility with the Ably SDK's API.
156-157: Removed nonisolated from unsubscribe methodAppropriately removed the nonisolated keyword from the unsubscribe method, as it's no longer needed with the class-based approach. The main actor ensures thread safety.
160-162: Correctly updated on methods to use @mainactor for callbackGood job updating both on method overloads to specify that callbacks will be executed on the main actor. This ensures thread safety for UI-related code and state updates.
Also applies to: 164-166
168-170: Removed nonisolated from off methodAppropriately removed the nonisolated keyword from the off method, as it's no longer needed with the class-based approach. The main actor ensures thread safety.
In 7d6acde we made the decision to use
actortypes to manage the mutable state of the SDK. Whilst we've been able to build the SDK using this approach, it has proved cumbersome.The public API is littered with
asyncmethods and getters for things that really should be synchronous, such as adding a subscription or fetching a room's state. This deviates from the JavaScript API and makes call sites look complicated.The use of
asyncfor fetching room state also means that it's impossible for a user to, for example, check a room's state and then reason about what operations they should be able to attempt to perform on the room (i.e. without the SDK immediately rejecting the attempt), because the room state can change under their feet. This is one of a class of threading-related problems that I describe in #260.Internally, we find ourselves having to write
awaitto do basic things like fetching the room lifecycle manager's state or to find out the arguments with which a mock method was called. And similarly, one internal component can't rely on the state that's isolated to a different internal isolation domain in order to make a decision, because this state might change under its feet.To address this, here I have decided to isolate all of the SDK's mutable state to the main actor. This simplifies the experience of developing the SDK, and it simplifies the experience of interacting with it from the main thread, which is where most of our users interact with it from.
(Note that this does not prevent us from using background tasks internally for moving heavy processing off the main thread; I'm specifically talking about state.)
This also moves us closer to being able to address #49 (keeping our internal state in sync with that of the underlying ably-cocoa objects).
Have removed:
actortypes (both in SDK and mocks)MainActortypes for which we still have it)There might be some simplifications that can be made for the lifecycle manager tests now, but I'm not going to look at them now because that class will change a lot for the single-channel work in #242.
I'll update the ably.com documentation in #254.
Even after this change,
Messages.subscribeis stillasyncbecause its implementation requires it to be. I believe that this is a bug; have created #257 for it.Resolves #232.
Summary by CodeRabbit