Skip to content

[ECO-5426][LiveObjects] Handle incoming object messages#1113

Closed
sacOO7 wants to merge 35 commits intomainfrom
feature/object-sync
Closed

[ECO-5426][LiveObjects] Handle incoming object messages#1113
sacOO7 wants to merge 35 commits intomainfrom
feature/object-sync

Conversation

@sacOO7
Copy link
Collaborator

@sacOO7 sacOO7 commented Jul 1, 2025

Summary by CodeRabbit

  • New Features

    • Added LiveObjects plugin support for channel state change notifications.
    • Introduced asynchronous, sequential processing of live object messages with lifecycle state management.
    • Implemented LiveMap and LiveCounter types with real-time synchronization, tombstoning, and garbage collection.
    • Added zero-value object creation and internal object pool management with periodic garbage collection.
    • Enhanced error handling with new error codes and domain-specific exceptions.
    • Improved deserialization with fallback for unknown enum codes.
    • Added comprehensive synchronization tracking and state transition handling.
    • Introduced detailed LiveMapEntry handling including tombstone and value resolution logic.
  • Bug Fixes

    • Improved robustness in message deserialization and error reporting.
    • Fixed handling of missing fields in object messages by populating from protocol messages.
  • Tests

    • Added extensive unit tests covering live objects, synchronization, object pools, counters, maps, error handling, and serialization.
    • Introduced test helpers for mocking and spying on internal live object components.
  • Refactor

    • Changed LiveCounter interface method to return Number instead of Long for counter values.

@coderabbitai
Copy link

coderabbitai bot commented Jul 1, 2025

## Walkthrough

This change implements the core synchronization and operation handling logic for Ably LiveObjects, including object sync, state management, and operation application for LiveMap and LiveCounter types. It introduces multiple new internal classes, updates interfaces, adds error handling, and provides comprehensive unit tests for all major components and flows.

## Changes

| Files / Groups                                                                 | Change Summary |
|-------------------------------------------------------------------------------|---------------|
| `lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java`, `ChannelBase.java`, `LiveCounter.java` | Added `handleStateChange` to `LiveObjectsPlugin` and integrated calls in channel state changes; changed `LiveCounter.value()` return type to `Number`. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt`, `DefaultLiveObjectsPlugin.kt` | Implemented async, sequential message processing, state management, and channel state change handling in `DefaultLiveObjects` and plugin. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/ErrorCodes.kt`, `Utils.kt`  | Added new error codes and helper for object-related errors. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/Helpers.kt`                 | Added extension to set channel serial from protocol messages. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/ObjectId.kt`                | Introduced `ObjectId` class with parsing and stringification. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt`           | Added `Unknown` enum values, new extension for object data validation, and doc clarifications. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt`          | Added `ObjectsManager` for sync, buffering, and applying object messages. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt`             | Added `ObjectsPool` for managing live objects and GC logic. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsSyncTracker.kt`      | Added `ObjectsSyncTracker` for sync sequence tracking. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt`, `MsgpackSerialization.kt` | Improved error handling and fallback for unknown enum codes in deserialization; simplified JSON encoding/decoding. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt`     | Introduced `BaseLiveObject` abstract class and `ObjectType` enum. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt`, `LiveCounterManager.kt` | Added internal `DefaultLiveCounter` class and manager for state/operation logic. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt`, `LiveMapManager.kt` | Added internal `DefaultLiveMap` class and manager for map state/operation logic. |
| `live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt`| Added `LiveMapEntry` data class and extension functions for tombstone and GC logic. |
| `live-objects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt`, `TestHelpers.kt` | Added test helpers for reflection, mocking, and dependency injection. |
| `live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectIdTest.kt`, `ObjectsSyncTrackerTest.kt` | Added unit tests for `ObjectId` and `ObjectsSyncTracker`. |
| `live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSerializationTest.kt`, `ObjectMessageSizeTest.kt` | Adjusted imports and literals in serialization tests. |
| `live-objects/src/test/kotlin/io/ably/lib/objects/unit/RealtimeObjectsTest.kt` | Renamed test class for clarity. |
| `live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt`, `ObjectsManagerTest.kt`, `ObjectsPoolTest.kt` | Added comprehensive tests for live objects orchestration, sync, and pool logic. |
| `live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt`, `livecounter/DefaultLiveCounterTest.kt`, `livecounter/LiveCounterManagerTest.kt`, `livemap/DefaultLiveMapTest.kt`, `livemap/LiveMapManagerTest.kt` | Added detailed unit tests for all live object types and their managers. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant ChannelBase
    participant LiveObjectsPlugin
    participant DefaultLiveObjects
    participant ObjectsManager
    participant ObjectsPool

    ChannelBase->>LiveObjectsPlugin: handleStateChange(channelName, state, hasObjects)
    LiveObjectsPlugin->>DefaultLiveObjects: handleStateChange(state, hasObjects)
    DefaultLiveObjects->>ObjectsManager: startNewSync() / endSync()
    DefaultLiveObjects->>ObjectsPool: resetToInitialPool() / clearObjectsData()
    DefaultLiveObjects->>ObjectsManager: handleObjectMessages() / handleObjectSyncMessages()
sequenceDiagram
    participant DefaultLiveObjects
    participant ObjectsManager
    participant ObjectsPool
    participant BaseLiveObject

    DefaultLiveObjects->>ObjectsManager: handleObjectMessages(list)
    ObjectsManager->>ObjectsPool: get(objectId) or createZeroValueObjectIfNotExists()
    ObjectsPool->>BaseLiveObject: applyObjectSync() / applyObject()
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Implement object sync (#1107, ECO-5426) The implementation includes sync sequence tracking, applying sync messages, and state updates.
Implement incoming object operations (#1114) Object operation messages are handled with validation, buffering, and application logic.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Change of LiveCounter.value() return type (lib/src/main/java/io/ably/lib/objects/LiveCounter.java) This is a minor API broadening unrelated to the core sync and operation implementation objectives.

Possibly related PRs

  • Setup : LiveObject plugin #1085: Initial introduction of the LiveObjectsPlugin interface and integration into channel and connection classes, related as the current PR extends the plugin interface with handleStateChange.

Suggested reviewers

  • ttypic

Poem

In fields of code where objects sync and flow,
The rabbits hop as counters rise and grow.
Maps are alive, their keys in bloom,
With every state change, new features loom.
Tests abound, the garden’s neat—
LiveObjects now are quite the treat!
🐇✨


<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

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


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 32b1a365bf327c9dde9a3fe72c032bcdc191f327 and 17164c45bec51976cd50b591547fec9740b0939e.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt

</details>

<details>
<summary>⏰ 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)</summary>

* GitHub Check: check (21)
* GitHub Check: check (24)
* GitHub Check: check (19)
* GitHub Check: check (29)
* GitHub Check: check
* GitHub Check: check-rest-okhttp
* GitHub Check: check-realtime-okhttp
* GitHub Check: check-liveobjects
* GitHub Check: check-rest
* GitHub Check: check-realtime
* GitHub Check: build

</details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details>
<summary>🧪 Generate unit tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post copyable unit tests in a comment
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `feature/object-sync`

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ably/ably-java&utm_content=1113):

- 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](https://www.coderabbit.ai/contact-us/support) 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](https://docs.coderabbit.ai/finishing-touches/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](https://docs.coderabbit.ai/guides/configure-coderabbit) 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](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@sacOO7 sacOO7 changed the title [ECO-5426] Implemented objects sync [ECO-5426] Implement objects sync Jul 1, 2025
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 1, 2025 10:46 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 1, 2025 10:47 Inactive
@sacOO7 sacOO7 force-pushed the feature/object-sync branch from d57e57b to a67ea0c Compare July 2, 2025 11:48
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 2, 2025 11:49 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 2, 2025 11:51 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 3, 2025 08:57 Inactive
@sacOO7 sacOO7 force-pushed the feature/object-sync branch from 1449ae8 to 38a7c34 Compare July 3, 2025 08:57
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 3, 2025 08:59 Inactive
context engineered prompts
1. Fixed build issues for some of the code.
2. Added spec annoations for code / code blocks
@sacOO7 sacOO7 force-pushed the feature/object-sync branch from 38a7c34 to 632a518 Compare July 3, 2025 09:31
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 3, 2025 09:32 Inactive
1. Added missing error codes and updated error handling for the same
2. Created separate class for ObjectId with static constructor
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 3, 2025 12:05 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 3, 2025 12:07 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 4, 2025 12:12 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 4, 2025 12:15 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 4, 2025 13:51 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 4, 2025 13:53 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 4, 2025 14:19 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 4, 2025 14:21 Inactive
@sacOO7 sacOO7 changed the title [ECO-5426] Implement objects sync [ECO-5426][LiveObjects] Handle incoming object messages Jul 7, 2025
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 7, 2025 13:03 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 10, 2025 12:06 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 10, 2025 15:28 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1113/javadoc July 10, 2025 15:30 Inactive
… management

- Added impl. for handling channel attached state
- Updated ObjectSyncTracker along with related unit tests
@sacOO7 sacOO7 force-pushed the feature/object-sync branch from aa0fd50 to 557dac6 Compare July 11, 2025 05:08
@github-actions github-actions bot temporarily deployed to staging/pull/1113/features July 11, 2025 05:11 Inactive
Copy link
Contributor

@ttypic ttypic left a comment

Choose a reason for hiding this comment

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

Looks very good for me, added couple of minor suggestions

throw objectError("Invalid object id: $objectId")
}

val typeStr = parts[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

small thing but you can do like this:

val (typeStr, rest) = parts

/**
* Creates ObjectId instance from hashed object id string.
*/
fun fromString(objectId: String?): ObjectId {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would make objectId non-nullable here, if we send null, this means something went wrong somewhere before and it always better to throw earlier

}

internal fun LiveObjectsAdapter.setChannelSerial(channelName: String, protocolMessage: ProtocolMessage) {
if (protocolMessage.action == ProtocolMessage.Action.`object`) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would suggest early return to reduce nesting level

sacOO7 added 7 commits July 11, 2025 17:56
… Long

- Fixed serialization for those types, from double to long
- Added mocks for LiveMap and LiveCounter to TestHelpers
- Added tests for LiveMap and LiveCounter
- Added LiveMap/LiveCounter level objectstate validation
- Updated enums to include unknown type to skip processing of unknown actions
- Added more unit tests for LiveMap and LiveCounter
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: 11

♻️ Duplicate comments (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectId.kt (1)

21-24: Consider making objectId parameter non-nullable.

Based on the past review feedback, consider making the objectId parameter non-nullable to fail fast if null is passed. This would help catch issues earlier in the call chain.

-fun fromString(objectId: String): ObjectId {
-  if (objectId.isEmpty()) {
-    throw objectError("Invalid object id: $objectId")
-  }
+fun fromString(objectId: String): ObjectId {
+  require(objectId.isNotEmpty()) { "Object ID cannot be empty" }
🧹 Nitpick comments (9)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/RealtimeObjectsTest.kt (2)

7-7: Redundant “Test” suffix in class name

The class is already inside a test source-set/package; appending Test twice (RealtimeObjectsTest) is verbose and inconsistent with neighbouring test classes (e.g. DefaultLiveObjectsTest). Consider renaming to RealtimeObjects or RealtimeObjectsSpec for brevity.


9-13: Method name repetition

testChannelObjectGetterTest() contains “test” twice. A single descriptive verb phrase (e.g. channelObjectsGetter) is clearer and aligns with JUnit-5/Kotlin-test naming conventions.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSerializationTest.kt (1)

45-47: Optional: favour assertIs<ObjectMessage> over manual cast

Instead of (actual as? ObjectMessage) followed by assertEquals, you can express intent more clearly and avoid the nullable path:

assertIs<ObjectMessage>(actual)
assertEquals(expected, actual)

This removes the silent null fallback and yields more readable failure messages.

Also applies to: 64-66, 172-180

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (1)

67-90: Consider using more specific exception type.

The tests use generic Exception type, but based on the implementation, canApplyOperation throws AblyException via objectError. Consider being more specific for better test clarity.

-    assertFailsWith<Exception>("Should throw error for null serial") {
+    assertFailsWith<AblyException>("Should throw error for null serial") {
       liveMap.canApplyOperation("site1", null)
     }

Apply similar changes to other assertions in this test.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (2)

48-55: Consider lazy initialization of GC job

The GC job is started immediately in the init block. Consider whether GC is needed from the start or if it could be lazily initialized when the first non-root object is added to save resources.


132-156: Consider adding GC performance monitoring

The GC interval runs every 5 minutes but has no timeout or performance monitoring. For large object pools, the GC operation could take significant time.

Consider adding:

  1. Metrics for GC duration
  2. A timeout for individual object GC operations
  3. Batching for very large pools
 private fun onGCInterval() {
+    val startTime = System.currentTimeMillis()
+    var removedCount = 0
     pool.entries.removeIf { (_, obj) ->
       if (obj.isEligibleForGc()) { 
+        removedCount++
         true 
       } else {
         obj.onGCInterval()
         false
       }
     }
+    val duration = System.currentTimeMillis() - startTime
+    Log.v(tag, "GC completed: removed=$removedCount, duration=${duration}ms, remaining=${pool.size}")
 }
live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)

57-91: Multiple unimplemented public interface methods

Several public LiveMap interface methods are not yet implemented. This could cause runtime failures if these methods are called.

Would you like me to help implement these LiveMap interface methods or create an issue to track their implementation?

live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (2)

41-47: Well-designed sequential processing architecture.

The single-threaded coroutine scope ensures thread safety for state management. The unlimited buffer is acceptable for channel messages but consider monitoring for potential memory issues in high-traffic scenarios.


192-192: Track the pending state change event implementation.

The TODO comment indicates that state change events are not yet implemented, which may be required for complete functionality.

Would you like me to create an issue to track the implementation of state change event emission?

Comment on lines 38 to 39
count = 123
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Compile-time type mismatch: Int supplied where Long? expected

ObjectCounter.count and ObjectCounterOp.amount were migrated to Long?. Passing bare integer literals (123, 10) will not compile in Kotlin.

-  count = 123
+  count = 123L
...
-  amount = 10
+  amount = 10L

Also applies to: 47-48

🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/fixtures/ObjectMessageFixtures.kt
at lines 38-39 and 47-48, integer literals are assigned to variables expecting
nullable Long types, causing a compile-time type mismatch. Fix this by
explicitly specifying the literals as Longs using the `L` suffix (e.g., `123L`,
`10L`) to match the expected `Long?` type.

Comment on lines 56 to 57
amount = 10 // Size: 8 bytes (number is always 8 bytes)
), // Total ObjectCounterOp size: 8 bytes
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Long literal required

amount is now Long?; use 10L to avoid a type mismatch.

-          amount = 10 // Size: 8 bytes (number is always 8 bytes)
+          amount = 10L // Size: 8 bytes (number is always 8 bytes)
📝 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.

Suggested change
amount = 10 // Size: 8 bytes (number is always 8 bytes)
), // Total ObjectCounterOp size: 8 bytes
amount = 10L // Size: 8 bytes (number is always 8 bytes)
), // Total ObjectCounterOp size: 8 bytes
🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
at lines 56 to 57, the value assigned to the nullable Long variable 'amount' is
an integer literal 10, causing a type mismatch. Change the literal from 10 to
10L to explicitly specify it as a Long type and match the expected variable
type.

Comment on lines 118 to 119
count = 50 // Size: 8 bytes
) // Total ObjectCounter size: 8 bytes
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Long literal required

-          count = 50 // Size: 8 bytes
+          count = 50L // Size: 8 bytes
📝 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.

Suggested change
count = 50 // Size: 8 bytes
) // Total ObjectCounter size: 8 bytes
count = 50L // Size: 8 bytes
) // Total ObjectCounter size: 8 bytes
🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
at lines 118 to 119, the numeric literal assigned to count should be explicitly
marked as a Long type. Fix this by appending an 'L' to the number 50 to indicate
it is a Long literal, ensuring type correctness.

Comment on lines 80 to 81
count = 100 // Size: 8 bytes (number is always 8 bytes)
), // Total ObjectCounter size: 8 bytes
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Long literal required

-          count = 100 // Size: 8 bytes (number is always 8 bytes)
+          count = 100L // Size: 8 bytes (number is always 8 bytes)
🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
at lines 80 to 81, the numeric literal assigned to count should be explicitly
marked as a Long type. Fix this by appending an 'L' to the number 100 to make it
a Long literal, ensuring the type matches the expected Long type in the code.

Comment on lines +32 to +50
override fun increment() {
TODO("Not yet implemented")
}

override fun incrementAsync(callback: Callback<Void>) {
TODO("Not yet implemented")
}

override fun decrement() {
TODO("Not yet implemented")
}

override fun decrementAsync(callback: Callback<Void>) {
TODO("Not yet implemented")
}

override fun value(): Long {
TODO("Not yet implemented")
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Track completion timeline for core counter operations.

The core counter operations (increment, decrement, value) are currently TODO stubs. While the architectural foundation with delegation to LiveCounterManager is sound, the incomplete implementation limits functionality.

Could you provide the timeline for implementing these core operations? This would help assess the readiness of the counter functionality.


🏁 Script executed:

#!/bin/bash
# Check if there are any tests that might be expecting these methods to work
rg -A 5 -B 5 "increment\(\)|decrement\(\)|value\(\)" --type kotlin | grep -E "(test|Test)" | head -20

Length of output: 103


Implement core LiveCounter operations – timeline needed
The methods in DefaultLiveCounter (increment, incrementAsync, decrement, decrementAsync, value) are all still TODO stubs (lines 32–50). While the delegation to LiveCounterManager and lifecycle setup look solid, none of the core counter operations are functional, and currently no tests exercise these methods.

• File: live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (lines 32–50)
• Missing implementations:
increment() / incrementAsync(callback: Callback<Void>)
decrement() / decrementAsync(callback: Callback<Void>)
value(): Long

Please provide an estimated timeline for completing these implementations so we can verify readiness and update tests accordingly.

🤖 Prompt for AI Agents
In
live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
between lines 32 and 50, the core LiveCounter methods increment, incrementAsync,
decrement, decrementAsync, and value are currently unimplemented stubs. To fix
this, implement these methods by delegating the increment and decrement
operations to the LiveCounterManager instance managing the counter state,
ensuring both synchronous and asynchronous variants properly update the counter
and invoke callbacks as needed. For the value() method, return the current
counter value from the LiveCounterManager. Provide an estimated timeline for
completing these implementations to coordinate testing and verification.

Comment on lines +92 to +106
@Test
fun `(RTLO4a4, RTLO4a5) canApplyOperation should return true when existing siteSerial is null or empty`() {
val liveMap: BaseLiveObject = DefaultLiveMap.zeroValue("map:testObject@1", mockk(), mockk())
assertTrue(liveMap.siteTimeserials.isEmpty(), "Initial siteTimeserials should be empty")

// RTLO4a4 - Get siteSerial from siteTimeserials map
// RTLO4a5 - Return true when siteSerial is null (no entry in map)
assertTrue(liveMap.canApplyOperation("site1", "serial1"),
"Should return true when no siteSerial exists for the site")

// RTLO4a5 - Return true when siteSerial is empty string
liveMap.siteTimeserials["site1"] = ""
assertTrue(liveMap.canApplyOperation("site1", "serial1"),
"Should return true when siteSerial is empty string")
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify internal state access pattern.

The test directly modifies siteTimeserials on line 103, which appears to be an internal mutable map. This might indicate that the map is not properly encapsulated.


🏁 Script executed:

#!/bin/bash
# Check if siteTimeserials is properly encapsulated in BaseLiveObject
ast-grep --pattern 'internal val siteTimeserials = $_'

Length of output: 205


Encapsulate Mutable siteTimeserials

The test at BaseLiveObjectTest.kt:103 directly mutates siteTimeserials, which is currently declared as an internal val in BaseLiveObject.kt:32. Exposing the mutable map this way leaks implementation details and ties tests to internal state.

• In live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt (line 32), change
internal val siteTimeserials = mutableMapOf<String, String>()
to a private property.
• Provide an internal (or protected) API—for example, a clearSiteSerial(siteId: String) or setSiteSerial(siteId: String, serial: String) method—that tests can call.
• Update the test in BaseLiveObjectTest.kt to use the new API rather than writing directly into the map.

This ensures proper encapsulation of siteTimeserials and keeps tests focused on the public behavior.

🤖 Prompt for AI Agents
In live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt at
line 32, change the declaration of siteTimeserials from an internal mutable map
to a private mutable map to encapsulate it. Add an internal or protected method
such as setSiteSerial(siteId: String, serial: String) or clearSiteSerial(siteId:
String) to allow controlled modification of siteTimeserials. Then, in
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt
between lines 92 and 106, update the test to use this new method instead of
directly mutating the siteTimeserials map. This will maintain encapsulation and
keep the test focused on the public API.

Comment on lines +141 to +143
// RTLO4a6 - Return false when message serial is less (lexicographically)
assertTrue(liveMap.canApplyOperation("site1", "serialA"),
"Should return false when message serial 'serialA' < siteSerial 'serial2'")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect comment.

The comment on line 142 says "Should return false" but the test correctly uses assertTrue. The comment should be updated to match the test expectation.

-    assertTrue(liveMap.canApplyOperation("site1", "serialA"),
-      "Should return false when message serial 'serialA' < siteSerial 'serial2'")
+    assertTrue(liveMap.canApplyOperation("site1", "serialA"),
+      "Should return true when message serial 'serialA' > siteSerial 'serial2'")
📝 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.

Suggested change
// RTLO4a6 - Return false when message serial is less (lexicographically)
assertTrue(liveMap.canApplyOperation("site1", "serialA"),
"Should return false when message serial 'serialA' < siteSerial 'serial2'")
// RTLO4a6 - Return false when message serial is less (lexicographically)
assertTrue(liveMap.canApplyOperation("site1", "serialA"),
"Should return true when message serial 'serialA' > siteSerial 'serial2'")
🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt
around lines 141 to 143, the comment incorrectly states "Should return false"
while the test asserts true. Update the comment to correctly reflect that the
test expects the method to return true when the message serial is less
lexicographically.

Comment on lines +46 to +65
@Test
fun `(RTLO4a1, RTLO4a2) canApplyOperation should accept ObjectMessage params and return boolean`() {
// RTLO4a1a - Assert parameter types and return type based on method signature using reflection
val method = BaseLiveObject::class.java.findMethod("canApplyOperation")

// RTLO4a1a - Verify parameter types
val parameters = method.parameters
assertEquals(2, parameters.size, "canApplyOperation should have exactly 2 parameters")

// First parameter should be String? (siteCode)
assertEquals(String::class.java, parameters[0].type, "First parameter should be of type String?")
assertTrue(parameters[0].isVarArgs.not(), "First parameter should not be varargs")

// Second parameter should be String? (timeSerial)
assertEquals(String::class.java, parameters[1].type, "Second parameter should be of type String?")
assertTrue(parameters[1].isVarArgs.not(), "Second parameter should not be varargs")

// RTLO4a2 - Verify return type
assertEquals(Boolean::class.java, method.returnType, "canApplyOperation should return Boolean")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the reflection method lookup.

The findMethod call on line 49 is missing parameter types, which will cause a NoSuchMethodException at runtime. The method canApplyOperation takes two String parameters.

Apply this fix:

-    val method = BaseLiveObject::class.java.findMethod("canApplyOperation")
+    val method = BaseLiveObject::class.java.getDeclaredMethod("canApplyOperation", String::class.java, String::class.java)
🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt
around lines 46 to 65, the reflection call to find the method
"canApplyOperation" is missing the parameter types, causing a
NoSuchMethodException. Fix this by specifying the parameter types (two String
classes) in the findMethod call to correctly locate the method signature.

Comment on lines +65 to +90
internal fun applyObject(objectMessage: ObjectMessage) {
validateObjectId(objectMessage.operation?.objectId)

val msgTimeSerial = objectMessage.serial
val msgSiteCode = objectMessage.siteCode
val objectOperation = objectMessage.operation as ObjectOperation

if (!canApplyOperation(msgSiteCode, msgTimeSerial)) {
// RTLC7b, RTLM15b
Log.v(
tag,
"Skipping ${objectOperation.action} op: op serial $msgTimeSerial <= site serial ${siteTimeserials[msgSiteCode]}; " +
"objectId=$objectId"
)
return
}
// should update stored site serial immediately. doesn't matter if we successfully apply the op,
// as it's important to mark that the op was processed by the object
siteTimeserials[msgSiteCode!!] = msgTimeSerial!! // RTLC7c, RTLM15c

if (isTombstoned) {
// this object is tombstoned so the operation cannot be applied
return;
}
applyObjectOperation(objectOperation, objectMessage) // RTLC7d
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add null safety check for operation cast.

Line 70 performs an unsafe cast of objectMessage.operation without checking if it's null first. This could cause a NullPointerException.

-    val objectOperation = objectMessage.operation as ObjectOperation
+    val objectOperation = objectMessage.operation as? ObjectOperation
+        ?: throw objectError("Missing operation in object message")

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt
around lines 65 to 90, the code performs an unsafe cast of
objectMessage.operation to ObjectOperation without checking for null, which can
cause a NullPointerException. To fix this, add a null check for
objectMessage.operation before casting it. If it is null, handle the case
appropriately, such as returning early or throwing a controlled exception, to
avoid unsafe casting and potential crashes.

// RTLC8b - Should skip if already merged
liveCounterManager.applyOperation(operation)

assertEquals(4L, liveCounter.data) // Should not change (still 0)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect comment.

The comment states "Should not change (still 0)" but the test expects the value to remain 4L, not 0.

-    assertEquals(4L, liveCounter.data) // Should not change (still 0)
+    assertEquals(4L, liveCounter.data) // Should not change (still 4)
📝 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.

Suggested change
assertEquals(4L, liveCounter.data) // Should not change (still 0)
assertEquals(4L, liveCounter.data) // Should not change (still 4)
🤖 Prompt for AI Agents
In
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt
at line 124, the comment next to the assertion incorrectly states "Should not
change (still 0)" while the expected value is 4L. Update the comment to
correctly reflect that the value should remain 4L, not 0, to match the assertion
and avoid confusion.

sacOO7 added 2 commits July 18, 2025 12:00
…andle exceptions

1. Added try catch to avoid crashing collector
2. Fixed defaultLiveObjects flaky tests
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 (3)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (2)

13-22: Consider the necessity of 24-hour grace period.

The 24-hour grace period seems quite conservative compared to the minimum requirement of > 2 minutes mentioned in the comment. This could potentially lead to memory bloat if many objects become eligible for GC.

Consider if a shorter period (2-4 hours) would provide sufficient safety while reducing memory overhead. The current value may be intentionally conservative for safety, but it's worth evaluating based on expected usage patterns.


48-55: Consider making gcJob immutable.

Since gcJob is only assigned once in the init block and never reassigned, it can be declared as val for better immutability.

-  private var gcJob: Job // Job for the garbage collection coroutine
+  private val gcJob: Job // Job for the garbage collection coroutine
live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (1)

194-203: State management method with pending event emission.

The state change logic is correct with appropriate guards against redundant changes. The TODO indicates incomplete event emission functionality.

Would you like me to help implement the state change event emission or open an issue to track this pending work?

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2dee529 and 32bbedb.

📒 Files selected for processing (2)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (1 hunks)
🧠 Learnings (2)
📓 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#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#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#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#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.
live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (6)

Learnt from: sacOO7
PR: #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: #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: #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: #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.

Learnt from: sacOO7
PR: #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: #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.

🧬 Code Graph Analysis (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (3)
live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)
  • onGCInterval (108-110)
live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt (1)
  • onGCInterval (191-191)
live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (1)
  • onGCInterval (66-69)
🧰 Additional context used
🧠 Learnings (2)
📓 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#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#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#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#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.
live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (6)

Learnt from: sacOO7
PR: #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: #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: #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: #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.

Learnt from: sacOO7
PR: #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: #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.

🧬 Code Graph Analysis (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (3)
live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)
  • onGCInterval (108-110)
live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseLiveObject.kt (1)
  • onGCInterval (191-191)
live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (1)
  • onGCInterval (66-69)
🔇 Additional comments (10)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (5)

1-9: Clean import structure and package organization.

The imports are well-organized and include all necessary dependencies for coroutines-based garbage collection and live object management.


62-107: Well-implemented pool operations with proper root object handling.

The pool operations correctly implement the required functionality:

  • Root object is properly preserved during reset and delete operations
  • Data clearing supports configurable event emission
  • Thread-safe operations for the sequential execution context

114-127: Excellent zero-value object creation logic.

The method correctly implements the spec requirements:

  • Checks for existing objects first (RTO6a)
  • Parses object ID to determine type (RTO6b)
  • Creates appropriate zero-value objects for Map/Counter types
  • Uses apply block to ensure objects are added to the pool (RTO6b4)

132-156: Robust garbage collection implementation with proper error handling.

The GC implementation is well-designed:

  • Two-phase GC: removes eligible objects then calls onGCInterval on remaining ones
  • Proper error handling that doesn't stop the GC loop
  • Cooperative cancellation with isActive check
  • Appropriate logging for debugging

162-167: Proper resource cleanup in dispose method.

The dispose method correctly handles cleanup in the right order:

  1. Cancel the GC job
  2. Cancel the coroutine scope
  3. Clear the pool

This ensures no memory leaks and proper resource management.

live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (5)

3-18: Well-defined state management and appropriate imports.

The ObjectsState enum provides clear lifecycle states for sync management, and the imports include all necessary coroutines components for the asynchronous architecture.


24-52: Excellent asynchronous architecture for thread-safe sequential processing.

The design effectively ensures thread safety:

  • Single-threaded coroutine scope with limitedParallelism(1) prevents concurrency issues
  • MutableSharedFlow with unlimited buffer prevents message loss
  • SupervisorJob provides proper fault isolation
  • Clear separation of concerns with ObjectsPool and ObjectsManager

101-147: Robust message handling with proper field enrichment and error handling.

The implementation correctly follows the specifications:

  • Sets channel serial per RTL15b
  • Validates message state before processing
  • Enriches ObjectMessage fields per OM2 spec
  • Sequential processing ensures correct operation order
  • Error handling prevents single message failures from stopping the flow

149-187: Comprehensive channel state handling following specifications.

The method correctly implements the required state transitions:

  • RTO4a: Proper sync initiation logic based on hasObjects and current state
  • RTO4b: Correct handling when no objects are present
  • Appropriate cleanup for detached/failed states without event emission
  • Thread-safe execution using sequentialScope

206-211: Proper resource cleanup with clear documentation.

The dispose method correctly handles cleanup:

  • Cancels the coroutine handler first
  • Disposes managed components (pool and manager)
  • Helpful comment about automatic garbage collection of event bus

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: 2

♻️ Duplicate comments (2)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)

106-126: Fix incorrect comment.

The comment states "Should not change (still 0)" but the test expects the value to remain 4.0, not 0.

-    assertEquals(4.0, liveCounter.data) // Should not change (still 0)
+    assertEquals(4.0, liveCounter.data) // Should not change (still 4)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt (1)

6-6: Remove the incorrect LiveMapManager import.

This import conflicts with the production import on line 5 and will cause compilation errors.

🧹 Nitpick comments (2)
lib/src/main/java/io/ably/lib/objects/LiveCounter.java (1)

57-57: Update the documentation to reflect the new return type.

The Javadoc comment still refers to "Long" but the method now returns Double.

-     * @return the current value of the counter as a Long.
+     * @return the current value of the counter as a Double.
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt (1)

635-818: Consider splitting this test into multiple focused test methods.

This test method contains 11 different test cases. For better maintainability and clarity, consider splitting each test case into its own @Test method with descriptive names. This would make it easier to identify which specific scenario fails and improve test readability.

Example refactoring for the first few cases:

@Test
fun `calculateUpdateFromDataDiff should return empty map when no changes`() {
    val prevData = mapOf<String, LiveMapEntry>()
    val newData = mapOf<String, LiveMapEntry>()
    val result = livemapManager.calculateUpdateFromDataDiff(prevData, newData)
    assertEquals(emptyMap<String, String>(), result)
}

@Test
fun `calculateUpdateFromDataDiff should detect added entries`() {
    val prevData = mapOf<String, LiveMapEntry>()
    val newData = mapOf(
        "key1" to LiveMapEntry(
            isTombstoned = false,
            timeserial = "1",
            data = ObjectData(value = ObjectValue("value1"))
        )
    )
    val result = livemapManager.calculateUpdateFromDataDiff(prevData, newData)
    assertEquals(mapOf("key1" to "updated"), result)
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 32bbedb and 3d45982.

📒 Files selected for processing (12)
  • lib/src/main/java/io/ably/lib/objects/LiveCounter.java (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt (4 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt (5 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/livecounter/LiveCounterManager.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt (1 hunks)
🧠 Learnings (5)
📓 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#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#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#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.
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.
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.
lib/src/main/java/io/ably/lib/objects/LiveCounter.java (1)

Learnt from: sacOO7
PR: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (6)

Learnt from: sacOO7
PR: #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: #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: #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.

Learnt from: sacOO7
PR: #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: #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.

Learnt from: sacOO7
PR: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt (2)

Learnt from: sacOO7
PR: #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: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)

Learnt from: sacOO7
PR: #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.

🚧 Files skipped from review as they are similar to previous changes (8)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt
🧰 Additional context used
🧠 Learnings (5)
📓 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#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#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#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.
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.
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.
lib/src/main/java/io/ably/lib/objects/LiveCounter.java (1)

Learnt from: sacOO7
PR: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (6)

Learnt from: sacOO7
PR: #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: #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: #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.

Learnt from: sacOO7
PR: #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: #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.

Learnt from: sacOO7
PR: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt (2)

Learnt from: sacOO7
PR: #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: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)

Learnt from: sacOO7
PR: #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.

🔇 Additional comments (19)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (7)

1-28: Well-structured imports and package declaration.

The package structure and imports are comprehensive and appropriate for the test functionality. All imported classes are utilized in the test methods.


29-29: Standard test class declaration.

The class follows standard Kotlin test naming conventions.


31-47: Excellent test implementation for RTO4a protocol specification.

The test correctly verifies that when the channel becomes ATTACHED with HAS_OBJECTS flag set to true, the sync sequence starts properly. The assertions appropriately check state transitions and method invocations.


49-77: Comprehensive test implementation for RTO4b protocol specification.

The test thoroughly covers the immediate sync completion scenario when HAS_OBJECTS flag is false. The setup, execution, and verification are comprehensive, covering all specification requirements (RTO4b1-RTO4b5).


79-95: Important edge case test for INITIALIZED state transitions.

The test correctly verifies that sync is always started when transitioning from INITIALIZED state, even with HAS_OBJECTS flag false. The verification of both startNewSync and endSync calls with appropriate parameters is accurate.


97-157: Thorough test of protocol message delegation to ObjectsManager.

The test comprehensively verifies RTO5 and RTO7 specifications by testing delegation of both OBJECT and OBJECT_SYNC protocol messages with realistic test data. The verification of method calls with correct parameters is accurate.


159-236: Excellent test for OM2 protocol specification compliance.

The test thoroughly verifies field population logic for ObjectMessage instances with missing fields. The sophisticated mocking approach to capture passed messages and comprehensive verification of field population rules (OM2a, OM2c, OM2e) demonstrates good test design for this critical protocol requirement.

lib/src/main/java/io/ably/lib/objects/LiveCounter.java (1)

61-61: Breaking API Change: Confirm All Consumers Handle Double Return

The return type of LiveCounter.value() has changed from Long to Double, which is a breaking change for any code—internal or external—that invokes this method. While no internal calls to value() were found in the current codebase, you must ensure:

  • Any downstream modules or external clients depending on LiveCounter.value() are updated to handle a Double return (e.g., adjusting variable types, rounding logic, or precision handling).
  • Documentation and release notes reflect this change and recommend a major-version bump.
  • Automated tests (if present) for any consumers are updated to expect Double instead of Long.
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (11)

1-9: LGTM - Well-structured test imports and package declaration.

The imports are appropriate and the package structure follows the expected conventions.


13-32: LGTM - Comprehensive state synchronization test.

The test correctly validates RTLC6, RTLC6b, and RTLC6c specifications, properly checking state override behavior and the difference calculation.


36-62: LGTM - Proper create operation merge test.

The test correctly validates RTLC6 and RTLC6d specifications, ensuring create operations are merged correctly during state synchronization.


66-85: LGTM - Appropriate error handling test.

The test correctly validates RTLC7 and RTLC7d3 specifications, ensuring unsupported actions throw appropriate exceptions with correct error codes.


88-103: LGTM - Valid create operation application test.

The test properly validates RTLC7, RTLC7d1, and RTLC8 specifications for applying counter create operations.


129-150: LGTM - Proper create operation merge logic test.

The test correctly validates RTLC8 and RTLC8c specifications for applying create operations when not already merged.


153-172: LGTM - Appropriate null handling test.

The test properly validates RTLC8, RTLC10, and RTLC10a specifications for handling null count values in create operations.


175-192: LGTM - Valid increment operation test.

The test correctly validates RTLC7, RTLC7d2, and RTLC9 specifications for applying counter increment operations.


195-214: LGTM - Proper error handling for missing payload.

The test appropriately validates RTLC7 and RTLC7d2 specifications, ensuring missing payload errors are handled correctly.


218-235: LGTM - Correct increment operation logic test.

The test properly validates RTLC9 and RTLC9b specifications for counter increment operations.


238-255: LGTM - Appropriate null amount handling test.

The test correctly validates RTLC9 and RTLC9b specifications for handling null amounts in increment operations.

sacOO7 added 2 commits July 23, 2025 10:26
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 (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1)

29-42: Logic is correct with minor optimization opportunity.

The method properly implements the buffering vs immediate application logic based on sync state.

Consider caching the state to avoid accessing it twice:

  internal fun handleObjectMessages(objectMessages: List<ObjectMessage>) {
+   val currentState = liveObjects.state
-   if (liveObjects.state != ObjectsState.SYNCED) {
+   if (currentState != ObjectsState.SYNCED) {
      // RTO7 - The client receives object messages in realtime over the channel concurrently with the sync sequence.
      // Some of the incoming object messages may have already been applied to the objects described in
      // the sync sequence, but others may not; therefore we must buffer these messages so that we can apply
      // them to the objects once the sync is complete.
-     Log.v(tag, "Buffering ${objectMessages.size} object messages, state: $liveObjects.state")
+     Log.v(tag, "Buffering ${objectMessages.size} object messages, state: $currentState")
      bufferedObjectOperations.addAll(objectMessages) // RTO8a
      return
    }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3d45982 and f7a5ce9.

📒 Files selected for processing (6)
  • live-objects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt (4 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt (5 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt (1 hunks)
🧠 Learnings (2)
📓 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#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#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#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.
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.
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.
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (4)

Learnt from: sacOO7
PR: #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: #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: #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.

Learnt from: sacOO7
PR: #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.

🧬 Code Graph Analysis (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (33-33)
🚧 Files skipped from review as they are similar to previous changes (5)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt
🧰 Additional context used
🧠 Learnings (2)
📓 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#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#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#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.
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.
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.
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (4)

Learnt from: sacOO7
PR: #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: #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: #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.

Learnt from: sacOO7
PR: #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.

🧬 Code Graph Analysis (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (33-33)
⏰ 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-liveobjects
  • GitHub Check: check-rest
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check (29)
  • GitHub Check: check-realtime
  • GitHub Check: check (24)
  • GitHub Check: check
  • GitHub Check: check (19)
  • GitHub Check: build
  • GitHub Check: check (21)
🔇 Additional comments (10)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (10)

12-23: Well-structured class with clear separation of concerns.

The class design follows good practices with proper encapsulation, clear field purposes, and comprehensive spec documentation. The dependency injection pattern with DefaultLiveObjects provides good testability.


49-66: Correctly implements sync sequence protocol.

The method properly handles the sync sequence lifecycle with appropriate checks for new sync sequences and completion. The deferred state event mechanism ensures proper event ordering for listeners.


73-81: Properly implements sync sequence initialization.

The method correctly clears stale data and sets up the new sync sequence according to the specification. The order of operations ensures clean state before starting the new sync.


88-99: Correctly implements sync sequence completion.

The method follows the proper order of operations: apply sync data first, then buffered operations, followed by cleanup. This ensures consistent object state and proper resource management.


105-115: Simple and correct utility methods.

These methods provide controlled access for clearing internal state, which is appropriate for the integration with DefaultLiveObjects.handleStateChange.


122-155: Correctly implements sync data application with proper object lifecycle management.

The method handles both existing and new objects appropriately, maintains object pool consistency by removing stale objects, and efficiently batches notifications after all updates are complete.


162-186: Well-designed object message application with robust error handling.

The zero-value object creation strategy elegantly handles operations for unknown object IDs, while the error handling appropriately skips invalid messages with proper logging without stopping the entire batch.


193-208: Proper sync message validation and application.

The method correctly validates that object states contain the required data (counter or map) and handles invalid messages gracefully with appropriate logging.


215-221: Clean object creation with proper type dispatch and validation.

The method uses appropriate pattern matching to create the correct object type and properly validates that the object state contains required data, throwing a meaningful error for invalid states.


223-227: Appropriate cleanup implementation.

The dispose method properly clears all internal state, providing clean resource management for the ObjectsManager lifecycle.

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

♻️ Duplicate comments (4)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (3)

51-51: Fix the reflection method lookup missing parameter types.

The findMethod call is missing parameter types, which will cause NoSuchMethodException at runtime since canApplyOperation requires two String parameters.

Apply this fix:

-    val method = BaseLiveObject::class.java.findMethod("canApplyOperation")
+    val method = BaseLiveObject::class.java.getDeclaredMethod("canApplyOperation", String::class.java, String::class.java)

143-145: Fix incorrect test comment.

The comment states "Should return false" but the test correctly uses assertTrue. The comment should match the test expectation.

-    assertTrue(liveMap.canApplyOperation("site1", "serialA"),
-      "Should return false when message serial 'serialA' < siteSerial 'serial2'")
+    assertTrue(liveMap.canApplyOperation("site1", "serialA"),
+      "Should return true when message serial 'serialA' > siteSerial 'serial2'")

105-105: Avoid direct mutation of internal state in tests.

The test directly mutates siteTimeserials, which is an internal mutable map. This breaks encapsulation and ties tests to implementation details.

Consider providing an internal test API in BaseLiveObject for controlled state manipulation, such as setSiteSerial(siteId: String, serial: String), and update the test to use this API instead of direct map access.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)

124-124: Fix incorrect comment.

The comment states "Should not change (still 0)" but the test expects the value to remain 4.0, not 0.

-    assertEquals(4.0, liveCounter.data.get()) // Should not change (still 0)
+    assertEquals(4.0, liveCounter.data.get()) // Should not change (still 4)
🧹 Nitpick comments (1)
live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)

40-74: Track TODO implementations for future completion.

All public API methods are currently stubbed with TODO. This is expected for the current PR scope but should be tracked for future implementation.

Would you like me to create an issue to track the implementation of these LiveMap public API methods (get, set, remove, entries, keys, values, size, and async variants)?

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 45fde3f and 529efd8.

📒 Files selected for processing (15)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (1 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/livecounter/LiveCounterManager.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (2 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1 hunks)
🧠 Learnings (8)
📓 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#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#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#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.
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.
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.
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (3)

Learnt from: sacOO7
PR: #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: #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: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (4)

Learnt from: sacOO7
PR: #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: #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: #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.

Learnt from: sacOO7
PR: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (3)

Learnt from: sacOO7
PR: #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: #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: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (8)

Learnt from: sacOO7
PR: #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: #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: #1095
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/LiveObjectTest.kt:1-6
Timestamp: 2025-06-05T10:24:28.789Z
Learning: In Kotlin, functions, classes, and other declarations within the same package are automatically accessible without explicit import statements. Do not suggest adding imports for functions/classes that are already in the same package.

Learnt from: sacOO7
PR: #1059
File: lib/src/test/java/io/ably/lib/chat/ChatMessagesTest.java:124-143
Timestamp: 2025-01-22T13:13:02.809Z
Learning: In test classes, prefer keeping setup code within individual test methods rather than extracting to shared helpers, as it makes tests more independent and readable by keeping the context visible within each test.

Learnt from: sacOO7
PR: #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: #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.

Learnt from: sacOO7
PR: #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.

Learnt from: sacOO7
PR: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (1)

Learnt from: sacOO7
PR: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt (2)

Learnt from: sacOO7
PR: #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: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)

Learnt from: sacOO7
PR: #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.

🧬 Code Graph Analysis (3)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (1)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getDefaultLiveObjectsWithMockedDeps (79-97)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (33-33)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getDefaultLiveCounterWithMockedDeps (113-124)
🚧 Files skipped from review as they are similar to previous changes (8)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjectsPlugin.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.kt
🧰 Additional context used
🧠 Learnings (8)
📓 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#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#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#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.
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.
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.
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (3)

Learnt from: sacOO7
PR: #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: #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: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (4)

Learnt from: sacOO7
PR: #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: #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: #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.

Learnt from: sacOO7
PR: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (3)

Learnt from: sacOO7
PR: #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: #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: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (8)

Learnt from: sacOO7
PR: #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: #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: #1095
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/LiveObjectTest.kt:1-6
Timestamp: 2025-06-05T10:24:28.789Z
Learning: In Kotlin, functions, classes, and other declarations within the same package are automatically accessible without explicit import statements. Do not suggest adding imports for functions/classes that are already in the same package.

Learnt from: sacOO7
PR: #1059
File: lib/src/test/java/io/ably/lib/chat/ChatMessagesTest.java:124-143
Timestamp: 2025-01-22T13:13:02.809Z
Learning: In test classes, prefer keeping setup code within individual test methods rather than extracting to shared helpers, as it makes tests more independent and readable by keeping the context visible within each test.

Learnt from: sacOO7
PR: #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: #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.

Learnt from: sacOO7
PR: #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.

Learnt from: sacOO7
PR: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (1)

Learnt from: sacOO7
PR: #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.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt (2)

Learnt from: sacOO7
PR: #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: #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.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)

Learnt from: sacOO7
PR: #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.

🧬 Code Graph Analysis (3)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/BaseLiveObjectTest.kt (1)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getDefaultLiveObjectsWithMockedDeps (79-97)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (1)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (33-33)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (1)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getDefaultLiveCounterWithMockedDeps (113-124)
⏰ 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 (21)
  • GitHub Check: check (29)
  • GitHub Check: check (24)
  • GitHub Check: check (19)
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest
  • GitHub Check: check-realtime
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check
  • GitHub Check: build
🔇 Additional comments (39)
live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt (4)

10-15: LGTM: Well-structured data class for live map entries.

The data class appropriately models live map entry state with optional tombstone metadata and data payload. Default values and nullable types are correctly applied.


21-33: LGTM: Robust tombstone checking logic.

The extension function correctly implements the tombstone checking logic per RTLM14 specification, handling both direct tombstoning and reference tombstoning with proper null safety.


39-53: LGTM: Comprehensive value resolution with tombstone awareness.

The value resolution logic correctly handles all scenarios including tombstones, primitive values, and object references with proper null safety and tombstone filtering.


58-61: LGTM: Correct GC eligibility implementation.

The GC eligibility check correctly validates tombstone status and grace period using current system time. The logic properly handles null tombstone timestamps.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt (6)

12-23: LGTM: Well-structured manager with clear responsibilities.

The class properly encapsulates sync data pooling and message buffering with clear documentation referencing specifications. The mutable collections are appropriately scoped as private.


29-42: LGTM: Correct buffering logic for non-synced states.

The message handling properly buffers operations during non-synced states per RTO7/RTO8 specifications, with appropriate logging and immediate application when synced.


164-186: LGTM: Robust operation application with comprehensive validation.

The method correctly handles missing operations, unknown actions, and creates zero-value objects for unknown IDs per RTO9 specification. The logging for skipped messages provides good debugging information.


193-208: LGTM: Proper sync message validation and collection.

The sync message handling correctly validates object state presence and content, with appropriate logging for invalid messages. The validation ensures either counter or map data is present.


215-221: LGTM: Type-safe object creation from state.

The factory method correctly creates objects based on state type with appropriate error handling using the established clientError utility for protocol violations.


223-227: LGTM: Proper resource cleanup in dispose method.

The dispose method correctly clears both internal collections, ensuring no memory leaks.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (5)

18-22: LGTM: Well-structured class with proper inheritance.

The class correctly implements LiveMap and extends BaseLiveObject with appropriate constructor parameters and default semantics. The private constructor with companion factory method is a good pattern.


29-29: LGTM: Thread-safe data storage choice.

Using ConcurrentHashMap is appropriate for thread-safe access from both public APIs and internal manager operations.


76-84: LGTM: Proper delegation to manager for core operations.

The delegation pattern to LiveMapManager for validation, state application, and operation handling is well-implemented and maintains separation of concerns.


86-89: LGTM: Efficient data clearing with diff calculation.

The clearData method efficiently calculates updates from current state to empty state before clearing, enabling proper change notifications.


100-103: LGTM: Standard zero-value factory implementation.

The factory method follows the established pattern for creating zero-value live objects as seen in the codebase.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (7)

14-23: LGTM: Well-defined configuration constants.

The GC configuration constants are appropriately defined with clear documentation explaining the 24-hour grace period rationale for avoiding serial ordering issues.


35-57: LGTM: Proper initialization with root object and GC setup.

The pool correctly initializes with a root object and starts the GC coroutine. The use of SupervisorJob ensures GC failures don't affect the parent scope.


77-80: LGTM: Safe pool reset preserving root object.

The reset logic correctly preserves the root object while clearing others, maintaining object reference stability as documented.


106-119: LGTM: Robust zero-value object creation.

The method correctly handles existing objects, parses object IDs for type determination, and creates appropriate zero-value objects with proper pool registration.


124-132: LGTM: Safe garbage collection with eligibility checks.

The GC interval handler correctly removes eligible objects and calls per-object GC methods on retained objects.


137-148: LGTM: Robust GC coroutine with error handling.

The GC coroutine properly handles exceptions without terminating the loop and uses isActive for clean cancellation. The exception logging provides good debugging information.


154-159: LGTM: Complete resource cleanup in dispose method.

The disposal method correctly cancels the GC job, cancels the coroutine scope, and clears the pool, ensuring no resource leaks.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (6)

3-10: LGTM! Import structure is clear and appropriate.

The combination of wildcard and specific imports provides good clarity for the test helper dependencies.


21-45: LGTM! Well-structured channel mock setup.

The function creates a realistic channel mock with proper state management and flexible configuration options.


47-58: LGTM! Simple and effective mock factory functions.

The functions provide clean abstractions for creating test dependencies, and the size extension follows the established pattern for reflection-based field access.


65-97: LGTM! Comprehensive DefaultLiveObjects mocking setup.

The helper provides flexible mocking options with clear access to internal dependencies, enabling thorough testing of DefaultLiveObjects behavior.


109-124: LGTM! Consistent LiveCounter mocking pattern.

The helper maintains the same flexible mocking approach as other components, enabling controlled testing of DefaultLiveCounter behavior.


136-151: LGTM! Consistent LiveMap mocking pattern.

The helper follows the established pattern, providing uniform mocking capabilities across all live object types.

live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt (11)

1-9: LGTM! Appropriate imports for LiveCounterManager testing.

All necessary dependencies are imported for comprehensive testing of the LiveCounterManager component.


12-32: LGTM! Comprehensive test for state override functionality.

The test correctly validates RTLC6 specification requirements, including proper data override and update calculation.


35-62: LGTM! Correct test for create operation merging.

The test properly validates the merging of create operations during state synchronization according to RTLC6d requirements.


65-85: LGTM! Proper error handling test for unsupported actions.

The test correctly validates that unsupported operations throw appropriate exceptions with the right error codes.


87-103: LGTM! Correct test for counter create operation.

The test properly validates the application of counter create operations and the setting of the merged flag.


128-150: LGTM! Correct test for applying unmerged create operations.

The test properly validates the application of create operations when the merged flag is false, including proper data accumulation.


152-172: LGTM! Proper test for null count handling in create operations.

The test correctly validates that null counts default to 0 and the merged flag is properly set.


174-192: LGTM! Correct test for counter increment operations.

The test properly validates the application of counter increment operations according to RTLC specification requirements.


194-214: LGTM! Proper error handling test for missing payloads.

The test correctly validates that increment operations with missing payloads throw appropriate exceptions.


217-235: LGTM! Focused test for RTLC9b increment behavior.

The test properly validates the specific requirements of RTLC9b for counter increment operations.


237-255: LGTM! Proper test for null amount handling in increments.

The test correctly validates that null amounts in increment operations are treated as 0, resulting in no change to the counter value.

@sacOO7
Copy link
Collaborator Author

sacOO7 commented Aug 6, 2025

closing in favor of #1137

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.

LiveObject: Implement incoming object operations LiveObject: Implement object sync

2 participants