[AIT-276] feat: introduce ACK-based local application of LiveObjects ops#1194
[AIT-276] feat: introduce ACK-based local application of LiveObjects ops#1194
Conversation
…operations - Implemented synthetic ACK-based object message application logic (`publishAndApply`). - Added buffering and order-preserving application of ACK results during sync (`applyAckResult`). - Introduced `ObjectsOperationSource` enum to distinguish operation sources (ACK vs. channel). - Updated `applyObject` and related object-specific management functions to utilize the source enum. - Enhanced tests for ACK-based application and updated handling of unsupported operations (returns `false` instead of throwing).
WalkthroughThis PR introduces a site-code-aware acknowledgment-based local operation system for LiveObjects. It adds siteCode propagation through connection details, implements a Changes
Sequence DiagramsequenceDiagram
participant Client
participant DefaultRealtimeObjects
participant ObjectsManager
participant Channel
participant Server
Client->>DefaultRealtimeObjects: publishAndApply(operation)
DefaultRealtimeObjects->>Channel: publish(message)
DefaultRealtimeObjects->>Server: await ACK
Server-->>DefaultRealtimeObjects: PublishResult (serial, siteCode)
DefaultRealtimeObjects->>DefaultRealtimeObjects: validate serial & siteCode
DefaultRealtimeObjects->>DefaultRealtimeObjects: create synthetic message
DefaultRealtimeObjects->>ObjectsManager: applyObjectMessages(message, LOCAL)
ObjectsManager->>ObjectsManager: applyAckResult(buffer deferred)
ObjectsManager->>ObjectsManager: deduplicate via appliedOnAckSerials
ObjectsManager->>Client: apply locally + complete deferred
Note over DefaultRealtimeObjects,ObjectsManager: LOCAL source ensures<br/>siteTimeserials not updated<br/>CHANNEL source updates state
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt (1)
102-105: Consider asserting the returned apply status as part of these scenarios.
SinceapplyObject()now returnsBoolean, asserting it here would improve contract-level coverage.Suggested test tightening
- liveMap.applyObject(message, ObjectsOperationSource.CHANNEL) + val applied = liveMap.applyObject(message, ObjectsOperationSource.CHANNEL) + assertEquals(false, applied) ... - liveMap.applyObject(message, ObjectsOperationSource.CHANNEL) + val applied = liveMap.applyObject(message, ObjectsOperationSource.CHANNEL) + assertEquals(true, applied)Also applies to: 132-135
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt` around lines 102 - 105, The test should assert the Boolean return value from liveMap.applyObject(...) to cover the contract; update DefaultLiveMapTest to capture the result of applyObject(message, ObjectsOperationSource.CHANNEL) (and the other applyObject call around lines 132-135) and add assertions like assertFalse or assertTrue as appropriate based on the scenario before verifying siteTimeserials, so the test checks both the returned apply status and the side-effect on liveMap.siteTimeserials.liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt (1)
92-95: Consider assertingapplyObject()boolean result in these tests.
These cases already validate side effects; also asserting the returnedBooleanwould protect the new apply contract from regressions.Suggested test tightening
- liveCounter.applyObject(message, ObjectsOperationSource.CHANNEL) + val applied = liveCounter.applyObject(message, ObjectsOperationSource.CHANNEL) + assertEquals(false, applied) ... - liveCounter.applyObject(message, ObjectsOperationSource.CHANNEL) + val applied = liveCounter.applyObject(message, ObjectsOperationSource.CHANNEL) + assertEquals(true, applied)Also applies to: 119-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt` around lines 92 - 95, The tests call DefaultLiveCounterTest::liveCounter.applyObject(message, ObjectsOperationSource.CHANNEL) but don’t assert its boolean return; capture the result and assert it matches the expected outcome (e.g., assertFalse(result) when the operation is skipped and the siteTimeserials remains "serial2", assertTrue(result) when the operation should be applied). Update both occurrences (the call around the siteTimeserials "serial2" check and the similar block at lines 119-122) to assign the return value from liveCounter.applyObject(...) to a variable and assert the Boolean equals the expected applied/skipped state.liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt (2)
319-330: Address swallowed exception warning from static analysis.The
try-catchon line 323 catches and swallows exceptions when accessingadapter.getChannel(channelName).reason. While the exception is used as an optional cause for the createdAblyException, if an unexpected exception occurs (not just the channel not being available), it's silently ignored.Consider being more specific about what exceptions to catch, or log the swallowed exception:
🔧 Proposed fix to log swallowed exception
- val errorReason = try { adapter.getChannel(channelName).reason } catch (e: Exception) { null } + val errorReason = try { + adapter.getChannel(channelName).reason + } catch (e: Exception) { + Log.d(tag, "Could not retrieve channel reason during state change handling", e) + null + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt` around lines 319 - 330, The try-catch around adapter.getChannel(channelName).reason swallows any Exception, hiding unexpected errors; update the code in DefaultRealtimeObjects (around adapter.getChannel, channelName, errorReason, ablyException, objectsManager.failBufferedAcks) to avoid silent swallowing by either catching only the expected exception type(s) thrown when a channel is absent or by logging the caught exception before returning null; ensure the catch block records the exception (e.g., logger.warn or logger.error with the exception) so unexpected failures are visible while still allowing errorReason to be null for building the AblyException.
212-221: Early returns silently degrade to echo-based application.When
siteCodeis null orserialsis unavailable/wrong length, the function logs an error and returns early, causing operations to be applied only when echoed from the server. This is a reasonable fallback, but consider whether callers should be aware of this degraded behavior.If this degradation is intentional and acceptable (operations still eventually apply), the current approach is fine. If callers need to know about the failure to apply locally, consider returning a result indicating the outcome.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt` around lines 212 - 221, The current early-return behavior when adapter.connectionManager.siteCode is null or publishResult.serials is null/length-mismatch (logged via Log.e with tag) silently degrades to echo-based application; change the function to surface this outcome to callers instead of only logging and returning—either by returning a status/result (e.g., boolean or enum) indicating "appliedLocally", "degradedToEcho", or an error, or by invoking the existing callback/throwing a descriptive exception; update all references to siteCode, publishResult.serials, and objectMessages handling so callers can react to the degraded path rather than being unaware.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt`:
- Around line 319-330: The try-catch around
adapter.getChannel(channelName).reason swallows any Exception, hiding unexpected
errors; update the code in DefaultRealtimeObjects (around adapter.getChannel,
channelName, errorReason, ablyException, objectsManager.failBufferedAcks) to
avoid silent swallowing by either catching only the expected exception type(s)
thrown when a channel is absent or by logging the caught exception before
returning null; ensure the catch block records the exception (e.g., logger.warn
or logger.error with the exception) so unexpected failures are visible while
still allowing errorReason to be null for building the AblyException.
- Around line 212-221: The current early-return behavior when
adapter.connectionManager.siteCode is null or publishResult.serials is
null/length-mismatch (logged via Log.e with tag) silently degrades to echo-based
application; change the function to surface this outcome to callers instead of
only logging and returning—either by returning a status/result (e.g., boolean or
enum) indicating "appliedLocally", "degradedToEcho", or an error, or by invoking
the existing callback/throwing a descriptive exception; update all references to
siteCode, publishResult.serials, and objectMessages handling so callers can
react to the degraded path rather than being unaware.
In
`@liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt`:
- Around line 92-95: The tests call
DefaultLiveCounterTest::liveCounter.applyObject(message,
ObjectsOperationSource.CHANNEL) but don’t assert its boolean return; capture the
result and assert it matches the expected outcome (e.g., assertFalse(result)
when the operation is skipped and the siteTimeserials remains "serial2",
assertTrue(result) when the operation should be applied). Update both
occurrences (the call around the siteTimeserials "serial2" check and the similar
block at lines 119-122) to assign the return value from
liveCounter.applyObject(...) to a variable and assert the Boolean equals the
expected applied/skipped state.
In
`@liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt`:
- Around line 102-105: The test should assert the Boolean return value from
liveMap.applyObject(...) to cover the contract; update DefaultLiveMapTest to
capture the result of applyObject(message, ObjectsOperationSource.CHANNEL) (and
the other applyObject call around lines 132-135) and add assertions like
assertFalse or assertTrue as appropriate based on the scenario before verifying
siteTimeserials, so the test checks both the returned apply status and the
side-effect on liveMap.siteTimeserials.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (16)
lib/src/main/java/io/ably/lib/transport/ConnectionManager.javalib/src/main/java/io/ably/lib/types/ConnectionDetails.javaliveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.ktliveobjects/src/main/kotlin/io/ably/lib/objects/ErrorCodes.ktliveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.ktliveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsOperationSource.ktliveobjects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.ktliveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.ktliveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.ktliveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.ktliveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.ktliveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.ktliveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.ktliveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.ktliveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.ktliveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt
introduce ACK-based local application of LiveObjects ops
publishAndApply).applyAckResult).ObjectsOperationSourceenum to distinguish operation sources (ACK vs. channel).applyObjectand related object-specific management functions to utilize the source enum.falseinstead of throwing).Summary by CodeRabbit
Release Notes
New Features
Bug Fixes