Skip to content

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

Merged
ttypic merged 1 commit intomainfrom
AIT-296/buffered-ops
Mar 11, 2026
Merged

[AIT-296] fix: clear buffered operations unconditionally on ATTACHED (RTO4d)#1196
ttypic merged 1 commit intomainfrom
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 to ensure buffered operations are cleared, discarded, or applied at the correct times for consistent state after reconnects and server syncs.
  • Tests
    • Expanded test coverage and teardown to validate attach/sync flows, buffer behavior, and state transitions across diverse sync scenarios.

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

coderabbitai bot commented Mar 5, 2026

Walkthrough

Unconditional buffer clearing for bufferedObjectOperations was moved to channel ATTACHED handling (RTO4d) in DefaultRealtimeObjects; startNewSync no longer clears the buffer, preserving operations during sync initialization. Tests were added/expanded to validate attach, pre-attach, and server resync behaviors.

Changes

Cohort / File(s) Summary
Channel attach change
liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
Call to clearBufferedObjectOperations() moved to run unconditionally in the ChannelState.attached branch (RTO4d).
Sync init change
liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt
Removed clearBufferedObjectOperations() from startNewSync(), so buffered operations are preserved when a new sync starts.
Tests and infra
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
Added testInstances lifecycle, teardown, and extensive tests covering RTO4d/RTO5 flows: ATTACHED buffer clearing, pre-ATTACHED operation discarding, post-sync application of buffered ops, and server-initiated resync scenarios.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(135,206,250,0.5)
    participant Channel
    end
    rect rgba(144,238,144,0.5)
    participant RealtimeObjects
    end
    rect rgba(255,228,181,0.5)
    participant ObjectsManager
    end
    rect rgba(255,182,193,0.5)
    participant Server
    end

    Channel->>RealtimeObjects: onStateChange(ATTACHED)
    RealtimeObjects->>RealtimeObjects: clearBufferedObjectOperations()  --(RTO4d)
    RealtimeObjects->>ObjectsManager: maybeStartSync() / request sync
    ObjectsManager->>Server: initiateSync()  -- buffer preserved during init
    Server-->>ObjectsManager: OBJECT_SYNC / sync data
    ObjectsManager->>RealtimeObjects: completeSync(syncData)
    RealtimeObjects->>RealtimeObjects: applyBufferedOperations()  -- post-sync apply
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nudged the buffer at attach’s bright light,
Cleared right on ATTACHED, then set things to right.
Syncs start with treasure still safe in the den,
Resyncs and tests hoppity-hop now and then.
sniff-sniff — celebrated in carrot-scented delight.

🚥 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 title clearly and specifically describes the main change: clearing buffered operations unconditionally on ATTACHED state with RTO4d reference.

✏️ 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 👍

@ttypic ttypic force-pushed the AIT-296/buffered-ops branch from d89628c to 61a681a Compare March 10, 2026 22:44
@github-actions github-actions bot temporarily deployed to staging/pull/1196/features March 10, 2026 22:44 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1196/javadoc March 10, 2026 22:47 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 (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

…(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
Collaborator

@sacOO7 sacOO7 left a comment

Choose a reason for hiding this comment

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

LGTM

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)

37-44: Route all instance construction through a registration factory.

The helper getDefaultRealtimeObjectsWithMockedDeps() creates real DefaultRealtimeObjects instances without registering them to testInstances. Of the 10 call sites in this file, only the 2 new tests (lines 300, 352) explicitly add instances. The remaining 8 tests create instances that survive past tearDown(), leaking collector jobs and scopes. Consolidate into a single factory method that always registers, similar to makeRealtimeObjects() in ObjectsManagerTest.kt.

🤖 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 37 - 44, The tests leak DefaultRealtimeObjects because
getDefaultRealtimeObjectsWithMockedDeps() constructs real DefaultRealtimeObjects
without adding them to testInstances; create a single factory (e.g.
makeDefaultRealtimeObjectsWithMockedDeps or modify
getDefaultRealtimeObjectsWithMockedDeps) that constructs the
DefaultRealtimeObjects, registers the instance into the existing testInstances
list, and returns it, and then update all call sites in
DefaultRealtimeObjectsTest to use this factory (similar to makeRealtimeObjects()
in ObjectsManagerTest.kt) so tearDown() will dispose and clear every created
instance.
🤖 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 37-44: The tests leak DefaultRealtimeObjects because
getDefaultRealtimeObjectsWithMockedDeps() constructs real DefaultRealtimeObjects
without adding them to testInstances; create a single factory (e.g.
makeDefaultRealtimeObjectsWithMockedDeps or modify
getDefaultRealtimeObjectsWithMockedDeps) that constructs the
DefaultRealtimeObjects, registers the instance into the existing testInstances
list, and returns it, and then update all call sites in
DefaultRealtimeObjectsTest to use this factory (similar to makeRealtimeObjects()
in ObjectsManagerTest.kt) so tearDown() will dispose and clear every created
instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a404d89-10f1-4e78-aaa1-55fe561aa6c3

📥 Commits

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

📒 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

@ttypic ttypic merged commit 25f9a2a into main Mar 11, 2026
14 checks passed
@ttypic ttypic deleted the AIT-296/buffered-ops branch March 11, 2026 10:37
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