Skip to content

[AIT-296] fix: clear buffered operations unconditionally on ATTACHED (RTO4d)#1196

Open
ttypic wants to merge 1 commit intoAIT-276/apply-on-ackfrom
AIT-296/buffered-ops
Open

[AIT-296] fix: clear buffered operations unconditionally on ATTACHED (RTO4d)#1196
ttypic wants to merge 1 commit intoAIT-276/apply-on-ackfrom
AIT-296/buffered-ops

Conversation

@ttypic
Copy link
Contributor

@ttypic ttypic commented Mar 5, 2026

  • Ensured BufferedObjectOperations is always cleared when channel state transitions to ATTACHED, regardless of the hasObjects flag.
  • Updated tests to verify buffer clearing behavior (RTO4d).

Spec: ably/specification#416
Js: ably/ably-js#2150

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of queued object operations during channel attachment and synchronization events to ensure correct operation timing and state consistency across reconnection scenarios.

@ttypic ttypic requested a review from sacOO7 March 5, 2026 11:23
@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

Walkthrough

Changes refactor buffer clearing logic for object operations: clearBufferedObjectOperations() is now called unconditionally during channel ATTACHED handling per RTO4d specification, rather than during sync start. The buffer is preserved during sync initialization instead of being discarded. Tests validate this behavior across attach, pre-attach, and resync scenarios.

Changes

Cohort / File(s) Summary
Buffer Clearing Refactor
liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
Moved clearBufferedObjectOperations() call to execute unconditionally in ChannelState.attached branch, removing it from the nested !hasObjects condition per RTO4d.
Sync Start Preservation
liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
Removed buffer clearing from startNewSync() method, preserving buffered operations during sync initialization instead of discarding them.
Test Coverage Expansion
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
Added test infrastructure (testInstances collection, tearDown cleanup) and comprehensive test cases validating RTO4d/RTO5a behavior: ATTACHED state buffer clearing, pre-ATTACHED operation discarding, post-sync application, and server-initiated resync scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Poem

🐰 The buffer cleared at just the right time,
No more confusion—the sync flows sublime!
Operations preserved through the resync ballet,
RTO4d dances brightly today!
hippity-hop 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: clearing buffered operations unconditionally on ATTACHED with reference to RTO4d requirement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch AIT-296/buffered-ops

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/1196/features March 5, 2026 11:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1196/javadoc March 5, 2026 11:26 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt`:
- Around line 300-301: The test expects bufferedObjectOperations to be preserved
across server-initiated resyncs, but ObjectsManager.startNewSync currently
clears bufferedObjectOperations; update the implementation by removing the call
that empties the buffer so that the ObjectsManager.bufferedObjectOperations (or
BufferedObjectOperations collection) is not cleared during startNewSync,
ensuring the behavior matches the test (and spec RTO5a2b) and leaving any
buffered ops intact across resyncs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85bd7049-01ca-4888-839a-0787bcc94aa2

📥 Commits

Reviewing files that changed from the base of the PR and between f57632f and 8eb140f.

📒 Files selected for processing (3)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt

@ttypic ttypic force-pushed the AIT-296/buffered-ops branch from 8eb140f to d89628c Compare March 5, 2026 12:00
@github-actions github-actions bot temporarily deployed to staging/pull/1196/features March 5, 2026 12:01 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1196/javadoc March 5, 2026 12:03 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt (1)

34-41: Make instance cleanup automatic to avoid future test leaks.

Good addition of tearDown(). One maintainability gap: cleanup still depends on remembering to push every created DefaultRealtimeObjects into testInstances.

♻️ Small refactor to centralize tracking
 class DefaultRealtimeObjectsTest {

   private val testInstances = mutableListOf<DefaultRealtimeObjects>()
+
+  private fun trackedRealtimeObjects(relaxed: Boolean = false): DefaultRealtimeObjects =
+    getDefaultRealtimeObjectsWithMockedDeps(relaxed = relaxed).also { testInstances.add(it) }

   `@After`
   fun tearDown() {
     val cleanupError = AblyException.fromErrorInfo(ErrorInfo("test cleanup", 500))
     testInstances.forEach { it.dispose(cleanupError) }
     testInstances.clear()
   }

Then replace direct calls like getDefaultRealtimeObjectsWithMockedDeps() with trackedRealtimeObjects().

🤖 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/objects/DefaultRealtimeObjectsTest.kt`
around lines 34 - 41, Tests rely on manually adding each DefaultRealtimeObjects
to testInstances for teardown, which is error-prone; create a helper
trackedRealtimeObjects() that constructs a DefaultRealtimeObjects (delegating to
getDefaultRealtimeObjectsWithMockedDeps() or similar), automatically appends it
to the testInstances list, and return it to callers so tests no longer need to
push instances themselves; update tests to call trackedRealtimeObjects() instead
of getDefaultRealtimeObjectsWithMockedDeps() and keep tearDown() as-is to
dispose all tracked DefaultRealtimeObjects.
🤖 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/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt`:
- Around line 34-41: Tests rely on manually adding each DefaultRealtimeObjects
to testInstances for teardown, which is error-prone; create a helper
trackedRealtimeObjects() that constructs a DefaultRealtimeObjects (delegating to
getDefaultRealtimeObjectsWithMockedDeps() or similar), automatically appends it
to the testInstances list, and return it to callers so tests no longer need to
push instances themselves; update tests to call trackedRealtimeObjects() instead
of getDefaultRealtimeObjectsWithMockedDeps() and keep tearDown() as-is to
dispose all tracked DefaultRealtimeObjects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2cf77248-6664-448e-971d-94a0cbbccd10

📥 Commits

Reviewing files that changed from the base of the PR and between 8eb140f and d89628c.

📒 Files selected for processing (4)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
💤 Files with no reviewable changes (1)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
  • liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aligns the Kotlin LiveObjects implementation with spec update RTO4d by ensuring buffered object operations are cleared on every channel transition to ATTACHED (independent of hasObjects), and by no longer discarding buffered operations when a new sync starts due to server-driven resyncs.

Changes:

  • Clear bufferedObjectOperations unconditionally on ChannelState.attached (RTO4d) and remove the now-redundant clear in the !hasObjects branch.
  • Stop clearing bufferedObjectOperations in ObjectsManager.startNewSync so buffered ops survive server-initiated resyncs.
  • Expand unit tests to cover attach/sync/resync flows and validate buffer clearing/preservation behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt Clears buffered ops unconditionally on ATTACHED and removes duplicate clearing in !hasObjects path.
liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt Removes clearing of buffered object ops from startNewSync, preserving buffered ops across resync restarts.
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt Adds tests for RTO4d attach clearing + resync behavior; introduces teardown for disposing created instances.
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt Updates the startNewSync test intent to reflect new buffering rules and adds buffering setup.
Comments suppressed due to low confidence (1)

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt:503

  • The test name/docstring says bufferedObjectOperations is NOT cleared by startNewSync, but the test never asserts the buffer size after calling startNewSync("seq-2"). Add an assertion after the second startNewSync to verify BufferedObjectOperations is unchanged (and optionally that the buffered op is still present), otherwise this test won’t catch regressions.
    // Start a new sync — should clear both appliedOnAckSerials and bufferedAcks (RTO21b)
    // but must NOT clear bufferedObjectOperations (now handled unconditionally by RTO4d in handleStateChange)
    objectsManager.startNewSync("seq-2")

    // RTO21b — appliedOnAckSerials and bufferedAcks cleared
    assertTrue(defaultRealtimeObjects.appliedOnAckSerials.isEmpty(),
      "appliedOnAckSerials should be cleared on new sync")
    assertEquals(0, objectsManager.BufferedAcks.size,
      "bufferedAcks should be cleared on new sync")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@sacOO7
Copy link
Collaborator

sacOO7 commented Mar 10, 2026

Shared few review comments, once you go through them and address few (optional) and above copilot comments, will approve the PR 👍

…(RTO4d)

- Ensured `BufferedObjectOperations` is always cleared when channel state transitions to ATTACHED, regardless of the `hasObjects` flag.
- Updated tests to verify buffer clearing behavior (RTO4d).
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.

🧹 Nitpick comments (2)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt (2)

34-40: Hide instance registration behind a helper.

Now that teardown depends on testInstances, the manual .also { testInstances.add(it) } bookkeeping is easy to forget in the next test. A small factory/helper would keep construction and cleanup symmetric.

Example cleanup helper
private fun newRealtimeObjects(): DefaultRealtimeObjects =
  DefaultRealtimeObjects("testChannel", getMockObjectsAdapter())
    .also(testInstances::add)

Also applies to: 199-200, 251-252

🤖 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/objects/DefaultRealtimeObjectsTest.kt`
around lines 34 - 40, Create a small factory helper that constructs
DefaultRealtimeObjects and registers it into the existing testInstances list to
avoid repetitive .also { testInstances.add(it) } calls; e.g. add a private
function newRealtimeObjects() that calls DefaultRealtimeObjects("testChannel",
getMockObjectsAdapter()) and .also(testInstances::add), then update tests to
call newRealtimeObjects() wherever they currently create DefaultRealtimeObjects
manually (look for occurrences around the constructions that currently call
.also { testInstances.add(it) }) so teardown (tearDown using testInstances)
reliably cleans up all instances.

171-195: Assert the ATTACHED call order too.

This only proves the buffer is empty eventually. It will still pass if startNewSync(null) moves before clearBufferedObjectOperations(), but the implementation relies on the opposite order to avoid dropping ops in that window. Please pin that ordering here as well.

Possible assertion to add
verifyOrder {
  defaultRealtimeObjects.ObjectsManager.clearBufferedObjectOperations()
  defaultRealtimeObjects.ObjectsManager.startNewSync(null)
}
🤖 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/objects/DefaultRealtimeObjectsTest.kt`
around lines 171 - 195, The test currently only checks the buffer is empty
eventually; add an explicit ordering assertion to ensure
clearBufferedObjectOperations() is invoked before startNewSync(null) on
defaultRealtimeObjects.ObjectsManager. After calling
defaultRealtimeObjects.handleStateChange(ChannelState.attached, true) and the
existing assertWaiter, add a verifyOrder block that references
defaultRealtimeObjects.ObjectsManager.clearBufferedObjectOperations() followed
by defaultRealtimeObjects.ObjectsManager.startNewSync(null) to pin the required
call order.
🤖 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/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt`:
- Around line 34-40: Create a small factory helper that constructs
DefaultRealtimeObjects and registers it into the existing testInstances list to
avoid repetitive .also { testInstances.add(it) } calls; e.g. add a private
function newRealtimeObjects() that calls DefaultRealtimeObjects("testChannel",
getMockObjectsAdapter()) and .also(testInstances::add), then update tests to
call newRealtimeObjects() wherever they currently create DefaultRealtimeObjects
manually (look for occurrences around the constructions that currently call
.also { testInstances.add(it) }) so teardown (tearDown using testInstances)
reliably cleans up all instances.
- Around line 171-195: The test currently only checks the buffer is empty
eventually; add an explicit ordering assertion to ensure
clearBufferedObjectOperations() is invoked before startNewSync(null) on
defaultRealtimeObjects.ObjectsManager. After calling
defaultRealtimeObjects.handleStateChange(ChannelState.attached, true) and the
existing assertWaiter, add a verifyOrder block that references
defaultRealtimeObjects.ObjectsManager.clearBufferedObjectOperations() followed
by defaultRealtimeObjects.ObjectsManager.startNewSync(null) to pin the required
call order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2000163a-bb39-4206-a782-8ff1db90aa30

📥 Commits

Reviewing files that changed from the base of the PR and between d89628c and 61a681a.

📒 Files selected for processing (3)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
💤 Files with no reviewable changes (1)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants