Skip to content
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

[ECO-4948] Runs room checks prior to performing presence operations #135

Merged
merged 1 commit into from
Nov 21, 2024

Conversation

umair-ably
Copy link
Collaborator

@umair-ably umair-ably commented Nov 21, 2024

Follow up PR from ECO-4948

Runs room checks prior to performing presence operations. Also tidied up the example app.

Summary by CodeRabbit

  • New Features

    • Enhanced asynchronous handling for chat functionalities, improving responsiveness and organization.
    • Improved error handling for presence operations, ensuring actions are performed under optimal conditions.
  • Bug Fixes

    • Addressed potential race conditions in presence management.
  • Documentation

    • Updated comments in integration tests for better clarity and accuracy in test flow.

Copy link

coderabbitai bot commented Nov 21, 2024

Walkthrough

The pull request introduces significant updates to the ContentView struct in ContentView.swift, consolidating multiple asynchronous task calls into a single block for improved control flow. It also enhances the handling of message subscriptions and presence events by utilizing background tasks. The DefaultPresence class in DefaultPresence.swift is modified to include error handling for presence operations, ensuring they are executed only when the feature channel is ready. Additionally, the IntegrationTests in IntegrationTests.swift are reorganized for clarity without altering their underlying logic.

Changes

File Path Change Summary
Example/AblyChatExample/ContentView.swift Consolidated multiple .tryTask calls into a single block; updated message and presence handling to use background tasks; modified showOccupancy() to retrieve current state before subscribing.
Sources/AblyChat/DefaultPresence.swift Enhanced error handling in DefaultPresence class for presence operations; integrated error logging in various methods.
Tests/AblyChatTests/IntegrationTests.swift Renumbered comments in basicIntegrationTest for clarity; no changes to underlying logic.

Assessment against linked issues

Objective Addressed Explanation
Implement online status (occupancy and presence) (ECO-4948)

Possibly related PRs

Suggested reviewers

  • maratal

Poem

In the chat where bunnies dwell,
Asynchronous tasks now flow so well.
With presence bright and occupancy clear,
Our messages hop, bringing cheer!
So let’s celebrate this code delight,
With every change, our future is bright! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 27d748e and 370d444.

📒 Files selected for processing (3)
  • Example/AblyChatExample/ContentView.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (6 hunks)
  • Tests/AblyChatTests/IntegrationTests.swift (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • Example/AblyChatExample/ContentView.swift
  • Sources/AblyChat/DefaultPresence.swift
  • Tests/AblyChatTests/IntegrationTests.swift

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (11)
Example/AblyChatExample/ContentView.swift (6)

185-189: Add error handling in background Task for message subscription

In the showMessages() function, the background Task may encounter errors during the message subscription loop that are not currently handled. To prevent unhandled exceptions, consider wrapping the loop in a do-catch block.

Apply this diff to include error handling:

 Task {
+    do {
         for await message in messagesSubscription {
             withAnimation {
                 messages.insert(BasicListItem(id: message.timeserial, title: message.clientID, text: message.text), at: 0)
             }
         }
+    } catch {
+        // Handle error appropriately
+        print("Error receiving messages: \(error)")
+    }
 }

198-202: Add error handling in background Task for reaction subscription

In the showReactions() function, the background Task handling reactions may throw errors that are not caught. Wrap the subscription loop in a do-catch block to handle potential exceptions.

Apply this diff:

 Task {
+    do {
         for await reaction in reactionSubscription {
             withAnimation {
                 showReaction(reaction.displayedText)
             }
         }
+    } catch {
+        // Handle error appropriately
+        print("Error receiving reactions: \(error)")
+    }
 }

211-219: Add error handling in background Task for presence events

The Task in showPresence() that listens for presence events may encounter errors. To enhance robustness, wrap the for-await loop in a do-catch block.

Apply this diff:

 Task {
+    do {
         for await event in try await room().presence.subscribe(events: [.enter, .leave, .update]) {
             withAnimation {
                 let status = event.data?.userCustomData?["status"]?.value as? String
                 let clientPresenceChangeMessage = "\(event.clientID) \(event.action.displayedText)"
                 let presenceMessage = status != nil ? "\(clientPresenceChangeMessage) with status: \(status!)" : clientPresenceChangeMessage

                 messages.insert(BasicListItem(id: UUID().uuidString, title: "System", text: presenceMessage), at: 0)
             }
         }
+    } catch {
+        // Handle error appropriately
+        print("Error receiving presence events: \(error)")
+    }
 }

226-234: Add error handling in background Task for typing events

In showTypings(), the background Task subscribing to typing events lacks error handling. Consider adding a do-catch block to manage potential errors.

Apply this diff:

 Task {
+    do {
         for await typing in try await room().typing.subscribe(bufferingPolicy: .unbounded) {
             withAnimation {
                 typingInfo = "Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
                 Task {
                     try? await Task.sleep(nanoseconds: 1 * 1_000_000_000)
                     withAnimation {
                         typingInfo = ""
                     }
                 }
             }
         }
+    } catch {
+        // Handle error appropriately
+        print("Error receiving typing events: \(error)")
+    }
 }

248-252: Add error handling in background Task for occupancy updates

The Task in showOccupancy() that listens for occupancy events does not handle errors. Wrapping the loop in a do-catch block will help manage any exceptions that occur.

Apply this diff:

 Task {
+    do {
         for await event in try await room().occupancy.subscribe(bufferingPolicy: .unbounded) {
             withAnimation {
                 occupancyInfo = "Connections: \(event.presenceMembers) (\(event.connections))"
             }
         }
+    } catch {
+        // Handle error appropriately
+        print("Error receiving occupancy updates: \(error)")
+    }
 }

259-271: Add error handling in background Task for room status changes

In showRoomStatus(), errors within the Task subscribing to room status changes may go unhandled. Include a do-catch block to handle potential errors.

Apply this diff:

 Task {
+    do {
         for await status in try await room().onStatusChange(bufferingPolicy: .unbounded) {
             withAnimation {
                 if status.current.isAttaching {
                     statusInfo = "\(status.current)...".capitalized
                 } else {
                     statusInfo = "\(status.current)".capitalized
                     if status.current == .attached {
                         Task {
                             try? await Task.sleep(nanoseconds: 1 * 1_000_000_000)
                             withAnimation {
                                 statusInfo = ""
                             }
                         }
                     }
                 }
             }
         }
+    } catch {
+        // Handle error appropriately
+        print("Error receiving room status changes: \(error)")
+    }
 }
Tests/AblyChatTests/IntegrationTests.swift (2)

115-124: Consider making the occupancy verification more robust.

The current implementation uses a hardcoded 2-second sleep to wait for occupancy updates. This approach might be fragile in different environments or under varying load conditions.

Consider implementing a polling mechanism with a timeout instead:

-// It can take a moment for the occupancy to update from the clients entering presence above, so we'll wait 2 seconds here.
-try await Task.sleep(nanoseconds: 2_000_000_000)
+// Poll for occupancy updates with a timeout
+func waitForOccupancyUpdate(room: Room, expectedPresenceMembers: Int, timeout: TimeInterval = 5) async throws {
+    let start = Date()
+    while Date().timeIntervalSince(start) < timeout {
+        let occupancy = try await room.occupancy.get()
+        if occupancy.presenceMembers == expectedPresenceMembers {
+            return
+        }
+        try await Task.sleep(nanoseconds: 100_000_000) // 100ms
+    }
+    throw TimeoutError("Occupancy update didn't reach expected value within timeout")
+}
+
+try await waitForOccupancyUpdate(room: rxRoom, expectedPresenceMembers: 1)

Line range hint 125-136: Standardize the approach for timing-dependent verifications.

Similar to the previous section, this segment uses a hardcoded sleep for occupancy updates after presence leave operation.

Consider extending the polling mechanism to handle both presence enter and leave operations:

+// Add a new case to handle presence leave
+enum PresenceOperation {
+    case enter
+    case leave
+}
+
+func waitForOccupancyUpdate(room: Room, operation: PresenceOperation, timeout: TimeInterval = 5) async throws {
+    let expectedMembers = operation == .enter ? 1 : 0
+    let start = Date()
+    while Date().timeIntervalSince(start) < timeout {
+        let occupancy = try await room.occupancy.get()
+        if occupancy.presenceMembers == expectedMembers {
+            return
+        }
+        try await Task.sleep(nanoseconds: 100_000_000) // 100ms
+    }
+    throw TimeoutError("Occupancy update didn't reach expected value within timeout")
+}
Sources/AblyChat/DefaultPresence.swift (3)

98-105: LGTM! Consider adding retry mechanism for critical operations.

The error handling is well-implemented. Given that enter() is a critical operation for user presence, consider implementing a retry mechanism with exponential backoff for transient failures.


145-152: LGTM! Consider ensuring reliable cleanup.

The error handling is well-implemented. Since leave() is crucial for cleaning up presence state, consider implementing a mechanism to ensure eventual consistency of the presence set, even if the initial leave operation fails.


Line range hint 24-152: Excellent implementation of the room readiness checks.

The consistent error handling pattern across all presence operations creates a robust and maintainable implementation. A few architectural considerations for future improvements:

  1. Consider implementing a circuit breaker pattern for presence operations to prevent overwhelming the system during issues
  2. Add telemetry for tracking failed presence operations to help identify patterns
  3. Consider implementing presence state reconciliation mechanisms for handling edge cases
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5344d7f and 27d748e.

📒 Files selected for processing (3)
  • Example/AblyChatExample/ContentView.swift (2 hunks)
  • Sources/AblyChat/DefaultPresence.swift (6 hunks)
  • Tests/AblyChatTests/IntegrationTests.swift (2 hunks)
🧰 Additional context used
📓 Learnings (1)
Example/AblyChatExample/ContentView.swift (2)
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/ContentView.swift:27-33
Timestamp: 2024-11-12T15:07:39.465Z
Learning: In `ContentView.swift`, the `room()` method does not use force try; it uses `try await` and handles errors appropriately.
Learnt from: maratal
PR: ably-labs/ably-chat-swift#34
File: Example/AblyChatExample/ContentView.swift:35-118
Timestamp: 2024-11-12T15:07:31.865Z
Learning: Skip code review suggestions for `ContentView.swift` in the `Example/AblyChatExample` project.
🔇 Additional comments (8)
Example/AblyChatExample/ContentView.swift (1)

137-144: Ensure intentional error handling in consolidated .tryTask block

By consolidating multiple asynchronous functions into a single .tryTask, any error thrown by one function will prevent subsequent functions from executing. Please verify that this behavior is intended and acceptable for the application's flow.

Tests/AblyChatTests/IntegrationTests.swift (3)

137-176: LGTM! Comprehensive presence event testing.

The test coverage for presence events is thorough, including:

  • Verification of all presence event types (enter, update, leave)
  • Validation of custom data persistence
  • Testing from both client perspectives

178-196: LGTM! Thorough cleanup and verification.

The cleanup process is well-structured with proper verification of:

  • Room detachment
  • Status change confirmation
  • Room release
  • Object reference validation

112-114: LGTM! Room attachment is correctly placed before presence operations.

The addition of this step ensures that the room is properly attached before performing any presence operations, which is a critical prerequisite.

Let's verify the presence operation requirements in the codebase:

✅ Verification successful

Let me verify if these presence operations are properly guarded by room attachment by checking the surrounding context.


Let me check if there are any other presence operations in the codebase that might not be properly guarded by room attachment.


Based on the comprehensive analysis of the codebase, I can now generate the final response:

Room attachment is correctly implemented as a prerequisite for presence operations

The verification confirms that:

  • The room attachment is properly placed before any presence operations in the integration tests
  • The RoomLifecycleManager ensures proper attachment handling
  • The presence operations are consistently performed after room attachment across the codebase
  • The implementation follows the correct pattern where presence operations require an attached channel state
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that presence operations are consistently guarded by room attachment checks
# Look for presence operations and their relationship with room attachment

ast-grep --pattern 'try await $_.presence.$_($$$)'

Length of output: 1186


Script:

#!/bin/bash
# Get the context around presence operations to verify attachment
rg "presence\." Tests/AblyChatTests/IntegrationTests.swift -B 5 -A 1

Length of output: 3827


Script:

#!/bin/bash
# Search for room attachment patterns and presence operations in the entire codebase
rg "attach\(\)" -A 10

Length of output: 22248

Sources/AblyChat/DefaultPresence.swift (4)

24-31: LGTM! Well-structured error handling for presence operations.

The implementation properly ensures room readiness before performing presence operations, with appropriate error handling and logging. This aligns well with the PR objective of running room checks prior to presence operations.


48-55: LGTM! Consistent error handling pattern maintained.

The implementation follows the same robust error handling pattern as the get() method, maintaining consistency across the codebase.


121-129: LGTM! Comprehensive error handling implementation.

The implementation maintains consistency with other presence operations while providing detailed error information.


73-80: LGTM! Consider verifying error handling scenarios.

The implementation maintains the consistent error handling pattern. Consider adding integration tests to verify the behavior when the room is not ready for presence operations.

✅ Verification successful

Error handling is well-tested, no additional verification needed

The error handling for waitToBeAbleToPerformPresenceOperations is thoroughly tested in DefaultRoomLifecycleManagerTests.swift with comprehensive test cases covering:

  • Success scenario when channel is attaching and attach succeeds
  • Failure scenario when channel attach fails
  • Behavior when already attached
  • Error cases for detached state
  • Error cases for other invalid states

Each scenario includes proper error propagation and validation of error messages, making additional integration tests unnecessary.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for integration tests covering error scenarios
rg -l "isUserPresent.*throw" "Tests/"

Length of output: 37


Script:

#!/bin/bash
# Search for test files containing isUserPresent method
rg -l "isUserPresent" "Tests/"

Length of output: 28


Script:

#!/bin/bash
# Search for test files containing waitToBeAbleToPerformPresenceOperations
rg -l "waitToBeAbleToPerformPresenceOperations" "Tests/"

Length of output: 221


Script:

#!/bin/bash
# Search for presence-related test files
fd "Presence.*\.swift" "Tests/"

Length of output: 31


Script:

#!/bin/bash
# Let's check the content of DefaultRoomLifecycleManagerTests.swift to see how waitToBeAbleToPerformPresenceOperations is tested
rg "waitToBeAbleToPerformPresenceOperations" -A 10 "Tests/AblyChatTests/DefaultRoomLifecycleManagerTests.swift"

Length of output: 8982


Script:

#!/bin/bash
# Let's also check the mock implementation to understand the test coverage
rg "waitToBeAbleToPerformPresenceOperations" -A 5 "Tests/AblyChatTests/Mocks/MockFeatureChannel.swift"

Length of output: 512

…Channel.waitToBeAbleToPerformPresenceOperations` which was implemented as part of [1].

It also became necessary to clean up the example app as this uncovered race conditions between concurrently running Tasks.
Copy link
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

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

Left two minor suggestions. I'm not sure about changes in ContentView.swift wdyt @lawrence-forooghian

Example/AblyChatExample/ContentView.swift Show resolved Hide resolved
Tests/AblyChatTests/IntegrationTests.swift Show resolved Hide resolved
Copy link
Collaborator

@maratal maratal left a comment

Choose a reason for hiding this comment

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

Lgtm

@umair-ably umair-ably merged commit c5e5d97 into main Nov 21, 2024
12 checks passed
@umair-ably umair-ably deleted the ECO-4948-v2 branch November 21, 2024 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants