Skip to content

Conversation

@sacOO7
Copy link
Collaborator

@sacOO7 sacOO7 commented Jul 15, 2025

Summary by CodeRabbit

  • New Features

    • Introduced synchronization state events for live objects, enabling subscriptions to SYNCING and SYNCED states.
    • Added methods to query channel modes and state for live objects.
    • Implemented core read operations for live maps and counters, including get, entries, keys, values, and size.
    • Enhanced error handling with new error codes for channel mode and state validation.
    • Added global coroutine scope utility for asynchronous callback execution.
    • Added unsubscribe capability for live objects event subscriptions.
    • Added retrieval of channel options and ensured live objects initialization on channel creation.
  • Bug Fixes

    • Improved disposal and resource cleanup for live objects and event listeners.
    • Enhanced disposal method to support cancellation reasons and proper coroutine cancellation.
  • Documentation

    • Added and clarified documentation for public methods and state events.
    • Added specification references to live map interface methods.
  • Tests

    • Added comprehensive integration tests for live maps, counters, and object synchronization.
    • Introduced test helpers and fixtures for streamlined setup and validation.
    • Updated test setup to optionally control channel auto-attachment and initialized REST helper.
  • Chores

    • Refactored internal structure to centralize state management and event handling.
    • Improved test utilities and fixtures for more robust integration testing.
    • Consolidated imports and replaced wildcard imports with explicit ones for clarity.
    • Updated error reporting to wrap messages in error objects before disposal calls.

@coderabbitai
Copy link

coderabbitai bot commented Jul 15, 2025

Walkthrough

This update introduces major enhancements to the Live Objects feature set. It implements the getRoot() method for LiveObjects, completes public API methods for LiveMap, and establishes a robust objects state change notification system. The changes include new interfaces, error codes, validation helpers, and comprehensive integration tests, along with supporting test fixtures and REST helpers.

Changes

File(s) / Group Change Summary
Adapter and Adapter Interface Extensions
lib/src/main/java/io/ably/lib/objects/Adapter.java, lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java
Added methods for querying channel modes and state, with error logging and nullability annotations.
LiveObjects Interface
lib/src/main/java/io/ably/lib/objects/LiveObjects.java
Interface now extends ObjectsStateChange to integrate state change event handling.
LiveMap Interface
lib/src/main/java/io/ably/lib/objects/LiveMap.java
Added specification reference comments to method declarations; changed async callback types to ObjectsCallback.
LiveObjectsPlugin Interface
lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java
Clarified documentation for dispose methods.
ObjectsSubscription Interface
lib/src/main/java/io/ably/lib/objects/ObjectsSubscription.java
New interface for unsubscribable objects event subscriptions.
Objects State Change Interfaces and Enum
lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java, lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java
Introduced interfaces and enums for live objects sync state events and listener management.
Realtime ChannelBase
lib/src/main/java/io/ably/lib/realtime/ChannelBase.java
Added getOptions() method and ensured live objects instance initialization at channel creation.
DefaultLiveObjects Implementation
live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
Implemented getRoot() (sync and async), event subscription methods, improved disposal, and removed redundant code.
Error Codes
live-objects/src/main/kotlin/io/ably/lib/objects/ErrorCodes.kt
Added error codes for channel mode and state validation.
Helpers for Validation
live-objects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
Added validation helpers for channel mode and state checks.
ObjectsManager Class
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
Now inherits from ObjectsStateCoordinator, centralizes state change logic, and disposes listeners.
ObjectsState Management
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt
New internal enum, interfaces, and abstract class to manage and emit live objects sync state changes.
Callback Scope Utility
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt
Added global coroutine scope for launching suspend functions with callback support.
BaseLiveObject Refinements
live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt
Refined constructor, visibility, and imports; made isTombstoned internal and volatile.
DefaultLiveCounter Implementation
live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
Implemented value() with access validation; updated async callback types.
DefaultLiveMap Implementation
live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
Implemented core read-only methods (get, entries, keys, values, size) with access checks; async callbacks updated.
Integration Tests for Live Objects
live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveCounterTest.kt, DefaultLiveMapTest.kt, DefaultLiveObjectsTest.kt
Added comprehensive integration tests for live counters, maps, and objects, covering sync, operations, and removal.
Test Helpers and Fixtures
live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/PayloadBuilder.kt, RestObjects.kt, Utils.kt, fixtures/CounterFixtures.kt, fixtures/DataFixtures.kt, fixtures/MapFixtures.kt
Added test helpers and fixtures for REST operations, data generation, and object tree setup.
Test Setup Improvements
live-objects/src/test/kotlin/io/ably/lib/objects/integration/setup/IntegrationTest.kt, Sandbox.kt
Improved test setup: channel attach control, REST helper initialization, and sandbox REST object creation.
Removed Redundant Test
live-objects/src/test/kotlin/io/ably/lib/objects/integration/LiveObjectTest.kt
Removed redundant or superseded test.
DefaultLiveObjectsPlugin
live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt
Changed dispose calls to wrap string messages in clientError() for error reporting consistency.
Callback Interface Addition
lib/src/main/java/io/ably/lib/objects/ObjectsCallback.java
Added new generic callback interface for LiveObjects async operations.
LiveCounter Interface
lib/src/main/java/io/ably/lib/objects/LiveCounter.java
Updated async callback parameter types to ObjectsCallback.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LiveObjects
    participant ObjectsManager
    participant ObjectsStateCoordinator
    participant LiveMap

    User->>LiveObjects: getRoot()
    LiveObjects->>ObjectsManager: ensureSynced()
    ObjectsManager->>ObjectsStateCoordinator: (await SYNCED)
    ObjectsManager-->>LiveObjects: (sync complete)
    LiveObjects->>LiveMap: retrieve root map
    LiveObjects-->>User: return LiveMap
Loading
sequenceDiagram
    participant User
    participant LiveObjects
    participant ObjectsStateCoordinator

    User->>LiveObjects: on(event, listener)
    LiveObjects->>ObjectsStateCoordinator: register listener
    ObjectsStateCoordinator-->>User: return ObjectsSubscription

    Note over ObjectsStateCoordinator: On state change
    ObjectsStateCoordinator->>User: listener.onStateChanged(event)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Assessment against linked issues

Objective Addressed Explanation
Implement getRoot() method for LiveObjects as per spec (#1122, ECO-5076)
Implement LiveMap public API methods with spec compliance (#1124)
Implement methods for subscribing to Objects State Change callbacks (#1129)

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes detected related to the linked issues.

Suggested reviewers

  • ttypic

Poem

In the warren of code, where the live objects grow,
Rabbits have burrowed, and features now flow.
With roots now revealed and maps that can read,
State changes hop swiftly—listeners take heed!
Tests and helpers sprout up, as fixtures abound,
This patch is a carrot—so round and so sound!
🥕


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67cfb14 and 2ddd598.

📒 Files selected for processing (10)
  • lib/src/main/java/io/ably/lib/objects/LiveCounter.java (2 hunks)
  • lib/src/main/java/io/ably/lib/objects/LiveMap.java (7 hunks)
  • lib/src/main/java/io/ably/lib/objects/LiveObjects.java (7 hunks)
  • lib/src/main/java/io/ably/lib/objects/ObjectsCallback.java (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (6 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (2 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/UtilsTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt
  • lib/src/main/java/io/ably/lib/objects/LiveCounter.java
  • lib/src/main/java/io/ably/lib/objects/LiveMap.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
  • lib/src/main/java/io/ably/lib/objects/ObjectsCallback.java
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/UtilsTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt
  • lib/src/main/java/io/ably/lib/objects/LiveObjects.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: sacOO7
PR: ably/ably-java#1106
File: live-objects/src/main/kotlin/io/ably/lib/objects/serialization/Serialization.kt:25-29
Timestamp: 2025-06-23T14:18:25.315Z
Learning: In the ably-java codebase, the DefaultLiveObjectSerializer class methods like writeMsgpackArray will have their type signatures updated to be non-nullable since the calling sites ensure objects are never null when passed to these methods.
Learnt from: sacOO7
PR: ably/ably-java#1092
File: live-objects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt:21-32
Timestamp: 2025-06-03T09:15:15.338Z
Learning: In test utility code for the Ably Java Live Objects module, the team prefers to keep reflection-based field access utilities simple without additional error handling, allowing tests to fail fast if incorrect field names are used.
Learnt from: sacOO7
PR: ably/ably-java#1085
File: lib/src/main/java/io/ably/lib/objects/LiveObjects.java:0-0
Timestamp: 2025-05-20T13:12:19.013Z
Learning: The LiveObjects interface does not currently include public API methods for resource management (dispose) or change listeners, as these features are not yet implemented.
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, an extension property `State` (capital S) is defined on the `LiveObjects` interface in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to the internal `state` field by casting to `DefaultLiveObjects`. This allows tests to access internal state for verification purposes.
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, extension properties with capital letter names (like `State`, `ObjectId`) are defined in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to internal fields of concrete implementations through their public interfaces. For example, `LiveObjects.State` casts to `DefaultLiveObjects` to access the internal `state` field for testing purposes.
Learnt from: sacOO7
PR: ably/ably-java#1087
File: lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java:32-34
Timestamp: 2025-05-27T12:11:25.084Z
Learning: In LiveObjects implementation (lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java), the send method intentionally hardcodes queueEvents to true rather than respecting ably.options.queueMessages. This is because LiveObjects requires reliable message delivery to ensure proper state synchronization and acknowledgment, unlike other realtime components that may allow configurable queuing behavior.
Learnt from: sacOO7
PR: ably/ably-java#1113
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:16-16
Timestamp: 2025-08-01T05:50:33.016Z
Learning: In LiveMapManagerTest.kt, the private field `livemapManager` is used extensively in the `shouldCalculateMapDifferenceCorrectly` test method to test the `calculateUpdateFromDataDiff` functionality across multiple test scenarios, so it should not be removed as unused.
Learnt from: sacOO7
PR: ably/ably-java#1092
File: live-objects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt:48-61
Timestamp: 2025-06-03T09:15:18.827Z
Learning: User sacOO7 prefers simple test utilities without extensive error handling, believing tests should fail fast if incorrect field/method names are used rather than having defensive programming.
📚 Learning: in the ably-java liveobjects test code, extension properties with capital letter names (like `state`...
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, extension properties with capital letter names (like `State`, `ObjectId`) are defined in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to internal fields of concrete implementations through their public interfaces. For example, `LiveObjects.State` casts to `DefaultLiveObjects` to access the internal `state` field for testing purposes.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in the ably-java liveobjects test code, an extension property `state` (capital s) is defined on the ...
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, an extension property `State` (capital S) is defined on the `LiveObjects` interface in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to the internal `state` field by casting to `DefaultLiveObjects`. This allows tests to access internal state for verification purposes.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in the ably-java codebase, the defaultliveobjectserializer class methods like writemsgpackarray will...
Learnt from: sacOO7
PR: ably/ably-java#1106
File: live-objects/src/main/kotlin/io/ably/lib/objects/serialization/Serialization.kt:25-29
Timestamp: 2025-06-23T14:18:25.315Z
Learning: In the ably-java codebase, the DefaultLiveObjectSerializer class methods like writeMsgpackArray will have their type signatures updated to be non-nullable since the calling sites ensure objects are never null when passed to these methods.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: the liveobjects interface does not currently include public api methods for resource management (dis...
Learnt from: sacOO7
PR: ably/ably-java#1085
File: lib/src/main/java/io/ably/lib/objects/LiveObjects.java:0-0
Timestamp: 2025-05-20T13:12:19.013Z
Learning: The LiveObjects interface does not currently include public API methods for resource management (dispose) or change listeners, as these features are not yet implemented.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in liveobjects implementation (lib/src/main/java/io/ably/lib/objects/liveobjectsadapter.java), the s...
Learnt from: sacOO7
PR: ably/ably-java#1087
File: lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java:32-34
Timestamp: 2025-05-27T12:11:25.084Z
Learning: In LiveObjects implementation (lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java), the send method intentionally hardcodes queueEvents to true rather than respecting ably.options.queueMessages. This is because LiveObjects requires reliable message delivery to ensure proper state synchronization and acknowledgment, unlike other realtime components that may allow configurable queuing behavior.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in test utility code for the ably java live objects module, the team prefers to keep reflection-base...
Learnt from: sacOO7
PR: ably/ably-java#1092
File: live-objects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt:21-32
Timestamp: 2025-06-03T09:15:15.338Z
Learning: In test utility code for the Ably Java Live Objects module, the team prefers to keep reflection-based field access utilities simple without additional error handling, allowing tests to fail fast if incorrect field names are used.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: the sandbox.kt file in ably-java live-objects module already has comprehensive http retry mechanism ...
Learnt from: sacOO7
PR: ably/ably-java#1095
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/setup/IntegrationTest.kt:87-89
Timestamp: 2025-06-06T09:28:12.298Z
Learning: The Sandbox.kt file in ably-java live-objects module already has comprehensive HTTP retry mechanism using HttpRequestRetry with 5 retries, exponential backoff, and automatic retry on non-success responses and timeout exceptions.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: the ably-java project prefers to use the latest available versions of testing dependencies (includin...
Learnt from: sacOO7
PR: ably/ably-java#1095
File: gradle/libs.versions.toml:24-26
Timestamp: 2025-06-05T10:27:53.946Z
Learning: The ably-java project prefers to use the latest available versions of testing dependencies (including pre-release versions) when they contain relevant bug fixes, rather than sticking strictly to stable releases.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in the ably-java codebase, the defaultliveobjectserializer class uses intentional unsafe casting (`o...
Learnt from: sacOO7
PR: ably/ably-java#1106
File: live-objects/src/main/kotlin/io/ably/lib/objects/serialization/Serialization.kt:38-46
Timestamp: 2025-06-23T14:14:17.847Z
Learning: In the ably-java codebase, the DefaultLiveObjectSerializer class uses intentional unsafe casting (`objects as Array<ObjectMessage>`) without type validation in both writeMsgpackArray and asJsonArray methods. This is a deliberate design decision to keep the dynamically-loaded class simple and let ClassCastException occur naturally for type mismatches.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in the ably java liveobjects messagepack deserialization code, the `action` field in objectoperation...
Learnt from: sacOO7
PR: ably/ably-java#1106
File: live-objects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt:207-211
Timestamp: 2025-06-23T14:28:23.301Z
Learning: In the Ably Java LiveObjects MessagePack deserialization code, the `action` field in ObjectOperation is guaranteed to always be present in the protocol, so using a default value during deserialization is acceptable and won't mask real protocol errors.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in livemapmanagertest.kt, the private field `livemapmanager` is used extensively in the `shouldcalcu...
Learnt from: sacOO7
PR: ably/ably-java#1113
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:16-16
Timestamp: 2025-08-01T05:50:33.016Z
Learning: In LiveMapManagerTest.kt, the private field `livemapManager` is used extensively in the `shouldCalculateMapDifferenceCorrectly` test method to test the `calculateUpdateFromDataDiff` functionality across multiple test scenarios, so it should not be removed as unused.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
📚 Learning: in livemapmanagertest.kt, the private field `livemapmanager` is used in the `shouldcalculatemapdiffe...
Learnt from: sacOO7
PR: ably/ably-java#1113
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:16-16
Timestamp: 2025-08-01T05:50:33.016Z
Learning: In LiveMapManagerTest.kt, the private field `livemapManager` is used in the `shouldCalculateMapDifferenceCorrectly` test method to test the `calculateUpdateFromDataDiff` functionality, so it should not be removed as unused.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest
  • GitHub Check: check-realtime
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest-okhttp
  • GitHub Check: build
  • GitHub Check: check (24)
  • GitHub Check: check (29)
  • GitHub Check: check (19)
  • GitHub Check: check (21)
  • GitHub Check: check
🔇 Additional comments (10)
live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (10)

3-4: LGTM - Imports align with new state management infrastructure

The new imports for ObjectsStateChange, ObjectsStateEvent, AblyException, and CancellationException are correctly added to support the enhanced state management, event subscription, and improved error handling functionality.

Also applies to: 6-6, 12-12


25-25: LGTM - State management centralization

Good refactoring to use the externally defined ObjectsState enum instead of an internal one, promoting consistency across the LiveObjects system.


45-48: Excellent improvement - Channel-specific async scope

This addresses the previous concerns about using GlobalScope by providing a channel-specific scope for asynchronous operations. This enables proper lifecycle management and disposal when the channel is released.


54-54: LGTM - Clean synchronous wrapper

Good implementation using runBlocking to provide synchronous access to the async functionality.


68-70: LGTM - Proper async callback implementation

Excellent use of the new asyncScope.launchWithCallback pattern for consistent async callback handling across the LiveObjects API.


99-103: LGTM - Efficient async implementation

Good implementation using withContext to ensure sequential execution without creating new coroutines, addressing previous performance feedback. The validation flow (API configuration check → ensure synced → retrieve from pool) is correct.


72-72: LGTM - Consistent callback interface usage

Good standardization to use ObjectsCallback<T> instead of generic Callback<T> across all async methods, providing better type safety and API consistency.

Also applies to: 76-76, 80-80, 84-84


92-97: LGTM - Event subscription implementation follows specification

The event subscription methods correctly delegate to objectsManager and use the appropriate types. The offAll() method inclusion aligns with the specification and ably-js implementation as discussed in previous reviews.


164-164: LGTM - Consistent with state enum refactoring

Correct update to use the centralized ObjectsState.Initialized value.


198-206: Excellent error handling and resource management improvements

Great enhancements to the disposal mechanism:

  • Using AblyException instead of String provides better error context
  • Proper error chaining with CancellationException.initCause()
  • Smart selective cancellation - using cancelChildren() on sequentialScope preserves the scope for potential getRoot() calls while cleaning up ongoing operations
  • Comprehensive resource cleanup covering all components

This addresses previous feedback about standardizing error handling across the LiveObjects system.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/objects-getroot

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

❤️ Share
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this 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.

@sacOO7 sacOO7 changed the title [ECO-5076] Implemented code for getRoot method [ECO-5076][LiveObjects] getRoot method Jul 15, 2025
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 15, 2025 12:27 Inactive
1. Added getChannelModes and getChannelState methods to adapter
2. Added extension helper methods to adapter to ensure channel config. is valid
@sacOO7 sacOO7 force-pushed the feature/objects-getroot branch from 3de1532 to 111e15a Compare July 15, 2025 13:23
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 15, 2025 13:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 15, 2025 13:26 Inactive
1. Added ObjectsEvents and emitter for the same
2. Implemented stateChange mechanism for objectsState changes
3. updated getRootAsync to ensure objects are synced
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 15, 2025 14:56 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 15, 2025 14:58 Inactive
• Add ObjectsStateEvent enum for tracking Live Objects synchronization states (SYNCING/SYNCED)
• Implement ObjectsStateListener interface for receiving state change notifications
• Create ObjectsStateSubscription interface for managing listener subscriptions and cleanup
• Update LiveObjects, DefaultLiveObjects, ObjectsManager, ObjectsState, and Utils classes to support state management
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 16, 2025 08:05 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 16, 2025 08:07 Inactive
@sacOO7 sacOO7 changed the title [ECO-5076][LiveObjects] getRoot method [ECO-5076][LiveObjects] getRoot method and objects state change handling Jul 16, 2025
… polluting

LiveObjects
- Added impl. for the same in ObjectsState.kt
- Extended the impl. in objectsmanager
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 16, 2025 09:10 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 16, 2025 09:12 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 17, 2025 08:47 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 17, 2025 08:50 Inactive
@sacOO7 sacOO7 requested a review from ttypic July 17, 2025 08:54
/**
* Coroutine scope for handling callbacks asynchronously.
*/
private val callbackScope = CoroutineScope(Dispatchers.Default + CoroutineName("LiveObjectsCallback-$channelName"))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can use single scope to manage live objects, I don't think we need two. Scope is mostly to group coroutines together and cancel them at the same time. For sequential execution we just need context, I suggest:

private val scope =
    CoroutineScope(Dispatchers.Default + CoroutineName("LiveObjects-$channelName") + SupervisorJob())
private val sequentialContext =  Dispatchers.Default.limitedParallelism(1)  
// ...

override fun getRootAsync(callback: Callback<LiveMap>) {
    scope.launchWithCallback(callback) { getRootAsync() }
}

private suspend fun getRootAsync(): LiveMap = withContext(sequentialContext) {
       adapter.throwIfInvalidAccessApiConfiguration(channelName)
       objectsManager.ensureSynced(state)
       objectsPool.get(ROOT_OBJECT_ID) as LiveMap
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually, I was planning to do something like that, but we are already using sequentialScope in non-suspend methods initializeHandlerForIncomingObjectMessages and handleStateChange.

Copy link
Collaborator Author

@sacOO7 sacOO7 Jul 18, 2025

Choose a reason for hiding this comment

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

Okay, I updated getRootAsync to use withContext to avoid new coroutine overhead.
And removed callbackScope from DefaultLiveObjects.kt.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added GlobalCallbackScope to utils.
This will be shared by
DefaultLiveObjects, DefaultLiveMap and DefaultLiveCounter async public API methods.

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought about this for some time, I am not sure GlobalCallbackScope is good thing, because we never close it even if we release channel, also no sure it has any advantage over built-in GlobalScope. Ideally I would use internal scope bind to the channel lifecycle or RealtimeClient lifecycle

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm.. as per GlobalScope doc, global scope has limited use-cases and can run coroutines indefinitely since it's not meant for structured concurrency. Will create objects level scope and will dispose it when dispose method is called.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, updated code to have channel/objects lifecycle specific scope -> 8b5ea81


override fun off(listener: ObjectsStateChange.Listener) = objectsManager.off(listener)

override fun offAll() = objectsManager.offAll()
Copy link
Contributor

Choose a reason for hiding this comment

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

let's not expose offAll() at least for now. offAll, unsubscribeAll - are "gigantic foot guns" (c) Andy

Copy link
Collaborator Author

@sacOO7 sacOO7 Jul 18, 2025

Choose a reason for hiding this comment

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

Same exists in case of ably-js objects#offAll, need to disusss with team yes

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also, we have unsubscribeAll in case of object-subscriptions. You can check ably/specification#346

@sacOO7 sacOO7 force-pushed the feature/objects-getroot branch from 1bacaf5 to 113c1b3 Compare July 17, 2025 12:02
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 17, 2025 12:03 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 17, 2025 12:04 Inactive
@sacOO7 sacOO7 force-pushed the feature/objects-getroot branch from 113c1b3 to f737cad Compare July 17, 2025 12:10
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 17, 2025 12:11 Inactive
@sacOO7 sacOO7 force-pushed the feature/objects-getroot branch from f737cad to 67c2b62 Compare July 17, 2025 12:12
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 17, 2025 12:12 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 17, 2025 12:14 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 24, 2025 08:30 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 24, 2025 08:32 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 24, 2025 09:19 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 24, 2025 09:21 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 29, 2025 10:09 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 29, 2025 10:11 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 29, 2025 10:36 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 29, 2025 10:37 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features July 29, 2025 15:02 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc July 29, 2025 15:03 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features August 1, 2025 09:47 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc August 1, 2025 09:48 Inactive
@sacOO7 sacOO7 force-pushed the feature/objects-getroot branch from cc4335e to 67cfb14 Compare August 1, 2025 09:57
@github-actions github-actions bot temporarily deployed to staging/pull/1120/features August 1, 2025 09:57 Inactive
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

🧹 Nitpick comments (2)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (2)

62-79: Solid async callback implementation with robust error handling.

The nested try-catch pattern correctly handles both operation errors and callback errors separately. The distinction between AblyException and other exceptions is appropriate, and logging callback errors without rethrowing prevents cascade failures.

Consider making the error message more specific:

-            val ex = ablyException("Error executing operation", ErrorCode.BadRequest, cause = throwable)
+            val ex = ablyException("Error executing async operation", ErrorCode.BadRequest, cause = throwable)

81-98: Correct implementation for void operations.

The method correctly handles Unit-returning operations by passing null to callback.onSuccess(null), which is appropriate for Java interop with ObjectsCallback<Void>.

Consider extracting the common error handling logic to reduce duplication:

private fun handleCallbackExecution(callback: ObjectsCallback<*>, action: () -> Unit) {
    try { action() } catch (t: Throwable) {
        Log.e(tag, "Error occurred while executing callback's onSuccess handler", t)
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc4335e and 67cfb14.

📒 Files selected for processing (10)
  • lib/src/main/java/io/ably/lib/objects/LiveCounter.java (2 hunks)
  • lib/src/main/java/io/ably/lib/objects/LiveMap.java (7 hunks)
  • lib/src/main/java/io/ably/lib/objects/LiveObjects.java (7 hunks)
  • lib/src/main/java/io/ably/lib/objects/ObjectsCallback.java (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (6 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (2 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/UtilsTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
  • lib/src/main/java/io/ably/lib/objects/ObjectsCallback.java
  • lib/src/main/java/io/ably/lib/objects/LiveCounter.java
  • lib/src/main/java/io/ably/lib/objects/LiveMap.java
  • lib/src/main/java/io/ably/lib/objects/LiveObjects.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/UtilsTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: sacOO7
PR: ably/ably-java#1106
File: live-objects/src/main/kotlin/io/ably/lib/objects/serialization/Serialization.kt:25-29
Timestamp: 2025-06-23T14:18:25.315Z
Learning: In the ably-java codebase, the DefaultLiveObjectSerializer class methods like writeMsgpackArray will have their type signatures updated to be non-nullable since the calling sites ensure objects are never null when passed to these methods.
Learnt from: sacOO7
PR: ably/ably-java#1092
File: live-objects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt:21-32
Timestamp: 2025-06-03T09:15:15.338Z
Learning: In test utility code for the Ably Java Live Objects module, the team prefers to keep reflection-based field access utilities simple without additional error handling, allowing tests to fail fast if incorrect field names are used.
Learnt from: sacOO7
PR: ably/ably-java#1085
File: lib/src/main/java/io/ably/lib/objects/LiveObjects.java:0-0
Timestamp: 2025-05-20T13:12:19.013Z
Learning: The LiveObjects interface does not currently include public API methods for resource management (dispose) or change listeners, as these features are not yet implemented.
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, an extension property `State` (capital S) is defined on the `LiveObjects` interface in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to the internal `state` field by casting to `DefaultLiveObjects`. This allows tests to access internal state for verification purposes.
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, extension properties with capital letter names (like `State`, `ObjectId`) are defined in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to internal fields of concrete implementations through their public interfaces. For example, `LiveObjects.State` casts to `DefaultLiveObjects` to access the internal `state` field for testing purposes.
Learnt from: sacOO7
PR: ably/ably-java#1087
File: lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java:32-34
Timestamp: 2025-05-27T12:11:25.084Z
Learning: In LiveObjects implementation (lib/src/main/java/io/ably/lib/objects/LiveObjectsAdapter.java), the send method intentionally hardcodes queueEvents to true rather than respecting ably.options.queueMessages. This is because LiveObjects requires reliable message delivery to ensure proper state synchronization and acknowledgment, unlike other realtime components that may allow configurable queuing behavior.
Learnt from: sacOO7
PR: ably/ably-java#1113
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:16-16
Timestamp: 2025-08-01T05:50:33.016Z
Learning: In LiveMapManagerTest.kt, the private field `livemapManager` is used extensively in the `shouldCalculateMapDifferenceCorrectly` test method to test the `calculateUpdateFromDataDiff` functionality across multiple test scenarios, so it should not be removed as unused.
Learnt from: sacOO7
PR: ably/ably-java#1092
File: live-objects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt:48-61
Timestamp: 2025-06-03T09:15:18.827Z
Learning: User sacOO7 prefers simple test utilities without extensive error handling, believing tests should fail fast if incorrect field/method names are used rather than having defensive programming.
📚 Learning: in the ably-java liveobjects test code, extension properties with capital letter names (like `state`...
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.717Z
Learning: In the ably-java LiveObjects test code, extension properties with capital letter names (like `State`, `ObjectId`) are defined in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to internal fields of concrete implementations through their public interfaces. For example, `LiveObjects.State` casts to `DefaultLiveObjects` to access the internal `state` field for testing purposes.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt
📚 Learning: the sandbox.kt file in ably-java live-objects module already has comprehensive http retry mechanism ...
Learnt from: sacOO7
PR: ably/ably-java#1095
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/setup/IntegrationTest.kt:87-89
Timestamp: 2025-06-06T09:28:12.298Z
Learning: The Sandbox.kt file in ably-java live-objects module already has comprehensive HTTP retry mechanism using HttpRequestRetry with 5 retries, exponential backoff, and automatic retry on non-success responses and timeout exceptions.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: check-liveobjects
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check (24)
  • GitHub Check: build
  • GitHub Check: check (19)
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check (29)
  • GitHub Check: check (21)
  • GitHub Check: check-rest
  • GitHub Check: check-realtime
  • GitHub Check: check
🔇 Additional comments (3)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (3)

5-7: LGTM!

The new imports are appropriate and necessary for the ObjectsAsyncScope implementation.


56-60: Well-designed coroutine scope setup.

The use of SupervisorJob ensures child coroutine failures don't affect siblings, and Dispatchers.Default with CoroutineName provides good performance and debugging capabilities. The channel-specific naming is excellent for troubleshooting.


100-102: Proper cancellation implementation.

Using cancelChildren(cause) correctly cancels all child coroutines while preserving the scope, and accepting a CancellationException parameter allows for meaningful cancellation context.

@github-actions github-actions bot temporarily deployed to staging/pull/1120/javadoc August 1, 2025 09:59 Inactive
- Updated ObjectsAsyncScope to handle ObjectsCallback
- Added launchWithVoidCallback method to handle void callbacks
- Added unit tests covering all scenarios for ObjectsAsyncScope
@sacOO7
Copy link
Collaborator Author

sacOO7 commented Aug 6, 2025

Closing in favor of #1138

@sacOO7 sacOO7 closed this Aug 6, 2025
@ttypic ttypic deleted the feature/objects-getroot branch September 28, 2025 21:59
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