Skip to content

Conversation

@sacOO7
Copy link
Collaborator

@sacOO7 sacOO7 commented Aug 27, 2025

Summary by CodeRabbit

  • New Features

    • Live Objects honor a server-configurable garbage-collection grace period for tombstone retention (default 24 hours); runtime value used when provided by server.
  • Refactor

    • Connection accessor simplified from a manager-focused API to a single connection-facing method for API consistency.
    • GC lifecycle moved from a hardcoded constant to a runtime-configurable grace period propagated to object GC checks.
  • Tests

    • Added tests for grace-period retrieval (immediate/deferred/null) and for invoking objects API without the Live Objects plugin.

@coderabbitai
Copy link

coderabbitai bot commented Aug 27, 2025

Walkthrough

Replaces Adapter connection accessor from ConnectionManager to Connection; adds ConnectionDetails.objectsGCGracePeriod and surfaces it via ConnectionManager; ObjectsPool consumes a server-provided GC grace period at runtime and GC-related APIs were parameterized; tests and a RealtimeChannel test updated.

Changes

Cohort / File(s) Summary
Adapter API shift (ConnectionManager → Connection)
lib/src/main/java/io/ably/lib/objects/Adapter.java, lib/src/main/java/io/ably/lib/objects/ObjectsAdapter.java
Public API change: getConnectionManager()getConnection(); imports and implementations updated to use io.ably.lib.realtime.Connection.
ConnectionDetails & ConnectionManager plumbing
lib/src/main/java/io/ably/lib/types/ConnectionDetails.java, lib/src/main/java/io/ably/lib/transport/ConnectionManager.java
Adds ConnectionDetails.objectsGCGracePeriod: Long with MsgPack read support; ConnectionManager gains public Long objectsGCGracePeriod = null and assigns it from connectionDetails during onConnected.
Helpers & subscription API
liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt, liveobjects/src/main/kotlin/io/ably/lib/objects/TestHelpers.kt
Adds extension access to the underlying connectionManager and onGCGracePeriodUpdated subscription (invokes immediately if value present, or registers connected listener); test helper mocking updated to provide a mock adapter with connectionManager.
Objects GC behavior (runtime-configurable)
liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt, liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPoolDefaults.kt, liveobjects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt, liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt, liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt, liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
Introduces default GC_GRACE_PERIOD_MS, adds volatile gcGracePeriod overridden from server value, updates periodic GC to pass gcGracePeriod to isEligibleForGc and onGCInterval; method signatures changed to accept gcGracePeriod: Long.
Tests and realtime test updates
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt, liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt, liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt, lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java
Tests refactored to use getMockObjectsAdapter() and updated mocks; added tests covering GC grace-period immediate/deferred/null delivery; added RealtimeChannel test asserting Channel#getObjects() throws when LiveObjects plugin not installed.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor App
  participant Pool as ObjectsPool
  participant Adapter as ObjectsAdapter
  participant Conn as Connection
  participant CM as ConnectionManager
  participant Srv as Server

  rect rgba(230,240,255,0.5)
    note right of Pool: Initialization & GC grace retrieval
    App->>Pool: init()
    Pool->>Adapter: onGCGracePeriodUpdated(cb)
    Adapter->>Conn: getConnection()
    Conn->>CM: access connectionManager
    alt CM.objectsGCGracePeriod present
      CM-->>Adapter: value
      Adapter-->>Pool: cb(value)
    else
      Conn-->>Adapter: register connected listener
    end
  end

  rect rgba(235,255,235,0.5)
    note over Srv,CM: Connection established with server details
    Srv-->>CM: ConnectionDetails(objectsGCGracePeriod)
    CM->>CM: objectsGCGracePeriod = ...
    Conn-->>Adapter: emit connected
    Adapter-->>Pool: cb(CM.objectsGCGracePeriod or null)
    Pool->>Pool: gcGracePeriod updated or default retained
  end

  rect rgba(255,245,230,0.5)
    note right of Pool: Periodic GC loop uses runtime value
    loop every interval
      Pool->>Pool: for each object
      Pool->>Pool: obj.isEligibleForGc(gcGracePeriod)?
      alt eligible
        Pool->>Pool: remove object
      else
        Pool->>Pool: obj.onGCInterval(gcGracePeriod)
      end
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • ttypic

Poem

I nibble code in midnight light,
Server whispers grace for tombstones' flight,
I fetch the period, hop to keep,
GC hums softly while objects sleep. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request includes public API surface changes such as renaming getConnectionManager to getConnection and related import adjustments that are not described in the linked issues for GC period support, indicating these refactors fall outside the stated objectives. Consider separating the API signature changes and import refactors into a dedicated pull request or update the issue descriptions to explicitly include these changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues Check ❓ Inconclusive The pull request fully implements the server-provided GC grace period support and aligns with issue #1116, but there is no clear information on how it addresses the LiveObjects Kotlin read-only client objective from ECO-5056, making it uncertain whether that issue is satisfied. Please clarify the objectives of ECO-5056 and ensure that any required read-only client functionality is implemented or document why it is not applicable to this pull request.
✅ 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 summarizes the primary change of enabling server-provided garbage-collection grace periods and reflects the main objective of the pull request.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/liveobjects-use-server-gc-period

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/1158/features August 27, 2025 13:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 27, 2025 13:27 Inactive
- Add support for server-configured objectsGCGracePeriod from connection details,
with fallback to default 24-hour period. Updates ObjectsPool and all LiveObjects
- Implementations to use the server-provided value for tombstone cleanup timing.
@sacOO7 sacOO7 force-pushed the feature/liveobjects-use-server-gc-period branch from 9610b41 to c8c84e7 Compare August 27, 2025 13:37
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 27, 2025 13:38 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 27, 2025 13:40 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 28, 2025 09:15 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 28, 2025 09:18 Inactive
@sacOO7 sacOO7 force-pushed the feature/liveobjects-use-server-gc-period branch from 12ecee1 to 367a751 Compare August 28, 2025 09:49
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 28, 2025 09:49 Inactive
@sacOO7 sacOO7 marked this pull request as ready for review August 28, 2025 09:50
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 28, 2025 09:52 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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
liveobjects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt (1)

124-131: Fix publication/visibility race when tombstoning

tombstonedAt is written after the volatile isTombstoned. A GC thread may observe isTombstoned == true but a stale/null tombstonedAt. Write tombstonedAt first so the subsequent volatile write publishes it; optionally mark tombstonedAt volatile.

-  @Volatile
-  internal var isTombstoned = false // Accessed from public API for LiveMap/LiveCounter
-
-  private var tombstonedAt: Long? = null
+  @Volatile
+  internal var isTombstoned = false // Accessed from public API for LiveMap/LiveCounter
+
+  @Volatile
+  private var tombstonedAt: Long? = null
@@
-    isTombstoned = true
-    tombstonedAt = serialTimestamp?: System.currentTimeMillis()
+    tombstonedAt = serialTimestamp ?: System.currentTimeMillis()
+    isTombstoned = true
🧹 Nitpick comments (13)
lib/src/main/java/io/ably/lib/types/ConnectionDetails.java (1)

77-81: Clarify null semantics and units for objectsGCGracePeriod

Document that null means “server did not provide a value” and that clients must fall back to the default (24h). This reduces ambiguity for callers.

-    /**
-     * The duration in milliseconds used to retain tombstoned objects at client side.
-     */
-    public Long objectsGCGracePeriod;
+    /**
+     * The duration, in milliseconds, to retain tombstoned objects on the client.
+     * Null indicates the server did not provide a value; callers MUST fall back to the default (24 hours).
+     */
+    public Long objectsGCGracePeriod; // may be null
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt (1)

23-26: Ensure extension property stubbing is robust; prefer deterministic payloads

  • The tests rely on adapter.connectionManager. Since this is an extension property (Helpers.kt), ensure TestHelpers stubs it via mockkStatic("io.ably.lib.objects.HelpersKt") and an explicit stub for any().connectionManager to avoid brittle resolution.

  • For determinism/speed, avoid Random when constructing large strings.

-    val mockAdapter = getMockObjectsAdapter()
+    val mockAdapter = getMockObjectsAdapter()
     mockAdapter.connectionManager.maxMessageSize = Defaults.maxMessageSize // 64 kb
     assertEquals(65536, mockAdapter.connectionManager.maxMessageSize)

In TestHelpers.kt (not shown here), prefer:

 mockkStatic("io.ably.lib.objects.HelpersKt")
 val cm = mockk<ConnectionManager>(relaxed = true)
+every { any<ObjectsAdapter>().connectionManager } returns cm

And make payload generation deterministic:

-      clientId = CharArray(60 * 1024) { ('a'..'z').random() }.concatToString()
+      clientId = CharArray(60 * 1024) { 'a' }.concatToString()
-      clientId = CharArray(5 * 1024) { ('a'..'z').random() }.concatToString()
+      clientId = CharArray(5 * 1024) { 'a' }.concatToString()

Also applies to: 148-151

liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (1)

14-15: Bridge property is fine; watch out for test stubbing of extension property

The extension property keeps the adapter API stable while migrating to Connection. Ensure tests stub this extension explicitly (via mockkStatic and any().connectionManager) to avoid brittle behavior.

lib/src/main/java/io/ably/lib/transport/ConnectionManager.java (2)

103-104: Add doc and consider naming to make units explicit.

objectsGCGracePeriod is nullable and in milliseconds; document it for clarity and future readers. If you prefer stronger clarity, consider objectsGCGracePeriodMs (optional, keep consistent with existing fields).

Apply minimal docs:

-    public Long objectsGCGracePeriod = null;
+    /** Server-provided GC grace period in milliseconds for client-side tombstone retention; null if not provided. */
+    public Long objectsGCGracePeriod = null;

1295-1303: Validate server value before publishing to readers.

Guard against non-positive values to avoid accidental immediate GC or overflow if a bad value is ever sent.

-        objectsGCGracePeriod = connectionDetails.objectsGCGracePeriod;
+        Long gc = connectionDetails.objectsGCGracePeriod;
+        if (gc != null && gc <= 0) {
+            Log.w(TAG, "Ignoring non-positive objectsGCGracePeriod: " + gc);
+            gc = null;
+        }
+        objectsGCGracePeriod = gc;

If you want stronger visibility guarantees for readers in other threads (tests/plugins) consider:

  • making objectsGCGracePeriod volatile, or
  • always reading it in response to the Connected event (your Helpers.kt path likely already provides the happens-before).

Confirm your HelpersTest covers “negative value ignored” and “null → default (24h) fallback”.

liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1)

185-187: Slightly simplify and avoid key destructuring overhead.

Operate on values; no need to touch keys. Equivalent behavior on ConcurrentHashMap and a touch leaner.

-  override fun onGCInterval(gcGracePeriod: Long) {
-    data.entries.removeIf { (_, entry) -> entry.isEligibleForGc(gcGracePeriod) }
-  }
+  override fun onGCInterval(gcGracePeriod: Long) {
+    data.values.removeIf { entry -> entry.isEligibleForGc(gcGracePeriod) }
+  }

Ensure LiveMapEntry.isEligibleForGc(gcGracePeriod) also considers referenced object tombstones (for values that reference other LiveObjects) so we don’t leak dereferenced children.

liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt (1)

63-66: Simplify boolean and guard against nulls explicitly

Minor readability: avoid the == true pattern and coalesce null to false.

-internal fun LiveMapEntry.isEligibleForGc(gcGracePeriod: Long): Boolean {
-  val currentTime = System.currentTimeMillis()
-  return isTombstoned && tombstonedAt?.let { currentTime - it >= gcGracePeriod } == true
-}
+internal fun LiveMapEntry.isEligibleForGc(gcGracePeriod: Long): Boolean {
+  val currentTime = System.currentTimeMillis()
+  return isTombstoned && (tombstonedAt?.let { currentTime - it >= gcGracePeriod } ?: false)
+}
liveobjects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt (2)

149-152: Mirror the boolean simplification used for entries

Same readability nit as in LiveMapEntry: avoid == true.

-internal fun isEligibleForGc(gcGracePeriod: Long): Boolean {
-  val currentTime = System.currentTimeMillis()
-  return isTombstoned && tombstonedAt?.let { currentTime - it >= gcGracePeriod } == true
-}
+internal fun isEligibleForGc(gcGracePeriod: Long): Boolean {
+  val currentTime = System.currentTimeMillis()
+  return isTombstoned && (tombstonedAt?.let { currentTime - it >= gcGracePeriod } ?: false)
+}

205-225: Enforce documented lower bound (> 2 minutes) where the value is set

Docs now state a >2min requirement; ensure the configured value is clamped at assignment (ObjectsPool) so implementors of onGCInterval(gcGracePeriod) can rely on it.

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (3)

66-66: Prefer direct field assignment over reflective setPrivateField

ConnectionManager.objectsGCGracePeriod is a Java field; MockK relaxed mocks allow direct assignment. Reflection adds noise and brittleness.

-connManager.setPrivateField("objectsGCGracePeriod", 123L)
+connManager.objectsGCGracePeriod = 123L
-connManager.setPrivateField("objectsGCGracePeriod", 456L)
+connManager.objectsGCGracePeriod = 456L

Also applies to: 83-83


62-73: Also assert no listener registered via other connection APIs

You verify once(connected, …) isn’t used; consider verifying no other connection listener API was touched to avoid false negatives with relaxed mocks.


75-91: Add a test for min-GC clamp

If we clamp server values below 2 minutes (see ObjectsPool comment), add a test asserting the callback yields the clamped value when server returns small periods (e.g., 60_000 ms).

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

136-144: Minor: reduce repeated time reads

If GC scans many objects, consider computing val now = System.currentTimeMillis() once per cycle and threading it through to checks to avoid per-object syscalls. Optional; current approach is fine if pool size is modest.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 39c62f6 and 367a751.

📒 Files selected for processing (14)
  • lib/src/main/java/io/ably/lib/objects/Adapter.java (2 hunks)
  • lib/src/main/java/io/ably/lib/objects/ObjectsAdapter.java (2 hunks)
  • lib/src/main/java/io/ably/lib/transport/ConnectionManager.java (2 hunks)
  • lib/src/main/java/io/ably/lib/types/ConnectionDetails.java (2 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (3 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (3 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt (2 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (1 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (1 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt (1 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (7 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt (2 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (2 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt (4 hunks)
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-08-07T07:19:59.979Z
Learnt from: sacOO7
PR: ably/ably-java#1139
File: live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt:86-92
Timestamp: 2025-08-07T07:19:59.979Z
Learning: In DefaultLiveCounter.notifyUpdated method, sacOO7 prefers to keep the unchecked cast `update as LiveCounterUpdate` without type safety checks, as they are confident the type system guarantees the correct type will always be passed.

Applied to files:

  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
📚 Learning: 2025-08-14T10:29:31.544Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt:25-28
Timestamp: 2025-08-14T10:29:31.544Z
Learning: In MockK with relaxed = true, accessing Java fields on mocked objects automatically creates mock instances, so assignments like `mockAdapter.connectionManager.maxMessageSize = value` work correctly without needing to explicitly create and assign the nested mock object.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
📚 Learning: 2025-08-01T09:53:16.730Z
Learnt from: sacOO7
PR: ably/ably-java#1120
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt:0-0
Timestamp: 2025-08-01T09:53:16.730Z
Learning: In the ably-java LiveObjects test code, extension properties with capital letter names (like `State`, `ObjectId`) are defined in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to internal fields of concrete implementations through their public interfaces. For example, `LiveObjects.State` casts to `DefaultLiveObjects` to access the internal `state` field for testing purposes.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt
📚 Learning: 2025-08-06T09:22:40.964Z
Learnt from: sacOO7
PR: ably/ably-java#1135
File: live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt:100-134
Timestamp: 2025-08-06T09:22:40.964Z
Learning: In DefaultLiveObjects.kt, the object creation pattern using objectsPool.get() followed by withContext(sequentialScope.coroutineContext) { objectsPool.createZeroValueObjectIfNotExists() } is thread-safe because sequentialScope uses limitedParallelism(1) ensuring sequential execution, and createZeroValueObjectIfNotExists() performs an internal get() check before creating, preventing duplicate object creation even when multiple coroutines initially see null from the first get() call.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt
📚 Learning: 2025-08-01T05:54:07.024Z
Learnt from: sacOO7
PR: ably/ably-java#1113
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:640-640
Timestamp: 2025-08-01T05:54:07.024Z
Learning: In the ably-java codebase test files, JUnit's Assert.assertEquals method is used which has the signature assertEquals(String message, Object expected, Object actual) where the message parameter comes first, not last like in Kotlin test's assertEquals method.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
📚 Learning: 2025-08-15T10:25:24.011Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: lib/src/main/java/io/ably/lib/objects/ObjectsHelper.java:24-25
Timestamp: 2025-08-15T10:25:24.011Z
Learning: In the ably-java codebase, the ObjectsHelper.java log messages that reference "LiveObjects plugin not found" and "LiveObjects functionality" are also part of the deliberate exceptions to the renaming effort, similar to other "LiveObjectsPlugin" references, due to ongoing internal technical debate about isolating API naming from product naming as discussed in LODR-033.

Applied to files:

  • lib/src/main/java/io/ably/lib/transport/ConnectionManager.java
📚 Learning: 2025-08-07T07:17:33.340Z
Learnt from: sacOO7
PR: ably/ably-java#1137
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:6-6
Timestamp: 2025-08-07T07:17:33.340Z
Learning: In the ably-java LiveObjects test code, there are extension properties defined in TestHelpers.kt that provide access to private fields of classes for testing purposes. For example, `internal var DefaultLiveMap.LiveMapManager: LiveMapManager` allows tests to access the private `liveMapManager` field. These extension imports (like `import io.ably.lib.objects.unit.LiveMapManager`) should not be removed as they are necessary for test functionality and are not conflicting imports.

Applied to files:

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

Applied to files:

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

Applied to files:

  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
📚 Learning: 2025-08-01T10:30:27.049Z
Learnt from: sacOO7
PR: ably/ably-java#1130
File: live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt:241-241
Timestamp: 2025-08-01T10:30:27.049Z
Learning: In DefaultLiveMapTest.kt integration tests, operations are performed sequentially one after another, and subscription callback list updates happen one at a time, so thread-safe collections are not needed for collecting updates in test scenarios.

Applied to files:

  • liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
🧬 Code graph analysis (5)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt (1)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getMockObjectsAdapter (49-55)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt (1)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getMockObjectsAdapter (49-55)
lib/src/main/java/io/ably/lib/objects/Adapter.java (2)
lib/src/main/java/io/ably/lib/realtime/Connection.java (1)
  • Connection (19-170)
pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeClient.kt (1)
  • connection (13-32)
lib/src/main/java/io/ably/lib/objects/ObjectsAdapter.java (1)
lib/src/main/java/io/ably/lib/realtime/Connection.java (1)
  • Connection (19-170)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (3)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getMockObjectsAdapter (49-55)
liveobjects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt (1)
  • setPrivateField (22-26)
liveobjects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (37-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-realtime
  • GitHub Check: check-rest
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check
  • GitHub Check: check (21)
  • GitHub Check: check (19)
  • GitHub Check: check (24)
  • GitHub Check: check (29)
  • GitHub Check: check (29)
🔇 Additional comments (7)
lib/src/main/java/io/ably/lib/types/ConnectionDetails.java (1)

122-124: ConnectionDetails.objectsGCGracePeriod is sent in milliseconds—no change required.

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt (1)

22-22: Good move to centralized test fixture

Switching to getMockObjectsAdapter() simplifies setup and improves consistency across tests.

Also applies to: 47-47, 81-81, 110-110

liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (1)

5-5: Import is appropriate

ConnectionEvent is required by the new one-shot listener.

liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt (1)

112-115: All overrides updated with gcGracePeriod parameter

No stale overrides remain—BaseRealtimeObject declares onGCInterval(gcGracePeriod: Long) exactly once, and all implementations (e.g. DefaultLiveMap, DefaultLiveCounter) have been updated accordingly.

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

25-28: No getConnectionManager references found—no downstream breakage
Search across the codebase returned no calls to getConnectionManager(), so replacing getConnectionManager() with getConnection() poses no risk. The suggested deprecated shim remains purely optional for transitional support.

liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)

49-55: I've added searches to confirm the helper function definitions, usages of static mocking, and the signature of getChannel. Once we have results, we can finalize the review.

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

21-28: All call-sites migrated to getConnection(); no legacy getConnectionManager() remains. Only the internal Helpers.kt extension property references connectionManager for live-objects plugin and tests.

1. Encouraged use of global mocck for Adapter
2. Updated failing tests accordingly
3. Added unit tests covering all cases for retrieveObjectsGCGracePeriod
@sacOO7 sacOO7 force-pushed the feature/liveobjects-use-server-gc-period branch from 367a751 to c6e3303 Compare August 28, 2025 10:55
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 28, 2025 10:55 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 28, 2025 10:57 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (1)

52-65: Resolved: “already connected + null” edge-case; consider terminal-state fallback and minor Kotlin style fix

  • The added “already connected” guard addresses the callback-never-fired issue noted earlier. Thanks for closing that gap.
  • Two small follow-ups:
    • Style: remove the extra space before the colon in the parameter type.
    • Optional resilience: also fire the callback with null when the connection is already in a terminal/non-progressing state (failed/closing/closed/suspended), so callers aren’t left waiting indefinitely if no future connected event will occur.

Apply this diff within this hunk:

-internal fun ObjectsAdapter.retrieveObjectsGCGracePeriod(block : (Long?) -> Unit) {
+internal fun ObjectsAdapter.retrieveObjectsGCGracePeriod(block: (Long?) -> Unit) {
   connectionManager.objectsGCGracePeriod?.let {
     block(it)
     return
   }
   // If already connected, no further `connected` event is guaranteed; return immediately.
-  if (connection.state == ConnectionState.connected) {
-    block(connectionManager.objectsGCGracePeriod)
+  if (connection.state == ConnectionState.connected) {
+    block(null)
     return
   }
+  // Also bail out early for terminal/non-progressing states
+  when (connection.state) {
+    ConnectionState.failed,
+    ConnectionState.closing,
+    ConnectionState.closed,
+    ConnectionState.suspended -> {
+      block(null)
+      return
+    }
+    else -> { /* wait for connected */ }
+  }
   connection.once(ConnectionEvent.connected) {
     block(connectionManager.objectsGCGracePeriod)
   }
 }
🧹 Nitpick comments (1)
lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java (1)

2582-2603: Test correctly asserts plugin-absence error path

Asserts code (40019), status (400), and stable message fragment, matching the intended behavior when LiveObjects is not installed.

Optionally drop the throws clause (not needed with assertThrows):

-    public void channel_get_objects_throws_exception() throws AblyException {
+    public void channel_get_objects_throws_exception() {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 367a751 and c6e3303.

📒 Files selected for processing (6)
  • lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java (2 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (3 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (7 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt (2 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (2 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsPoolTest.kt
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: sacOO7
PR: ably/ably-java#1158
File: liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt:61-67
Timestamp: 2025-08-28T10:45:52.247Z
Learning: In the Ably LiveObjects system, the server will never send an objectsGCGracePeriod value less than 2 minutes (120000 ms), so client-side validation/clamping of this value is not required.
📚 Learning: 2025-08-28T10:45:52.247Z
Learnt from: sacOO7
PR: ably/ably-java#1158
File: liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt:61-67
Timestamp: 2025-08-28T10:45:52.247Z
Learning: In the Ably LiveObjects system, the server will never send an objectsGCGracePeriod value less than 2 minutes (120000 ms), so client-side validation/clamping of this value is not required.

Applied to files:

  • liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
📚 Learning: 2025-08-15T10:25:24.011Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: lib/src/main/java/io/ably/lib/objects/ObjectsHelper.java:24-25
Timestamp: 2025-08-15T10:25:24.011Z
Learning: In the ably-java codebase, the ObjectsHelper.java log messages that reference "LiveObjects plugin not found" and "LiveObjects functionality" are also part of the deliberate exceptions to the renaming effort, similar to other "LiveObjectsPlugin" references, due to ongoing internal technical debate about isolating API naming from product naming as discussed in LODR-033.

Applied to files:

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

Applied to files:

  • lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java
⏰ 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). (12)
  • GitHub Check: check-liveobjects
  • GitHub Check: build
  • GitHub Check: check
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check (21)
  • GitHub Check: check-rest
  • GitHub Check: check (29)
  • GitHub Check: check (24)
  • GitHub Check: check-realtime
  • GitHub Check: check (19)
  • GitHub Check: check (29)
🔇 Additional comments (4)
liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (3)

5-6: Required imports added appropriately

ConnectionEvent/ConnectionState imports are necessary for the new retrieval helper. Looks good.


15-15: Good: centralized access to ConnectionManager via adapter

The extension keeps adapter code concise and consistent with surrounding helpers.


52-65: Default GC grace period is initialized upfront; callback only overrides
The gcGracePeriod property in ObjectsPool is set to the 24 h default at declaration, before retrieveObjectsGCGracePeriod is ever called. The callback merely overrides this default if the server provides a value. No caller waits on the callback to establish the default.

lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java (1)

49-49: Import for assertThrows is correct

JUnit 4.13+ static import enables method reference usage below. Good.

@sacOO7 sacOO7 force-pushed the feature/liveobjects-use-server-gc-period branch from c6e3303 to 592fa58 Compare August 28, 2025 11:24
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 28, 2025 11:24 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 28, 2025 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: 0

♻️ Duplicate comments (2)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (2)

44-45: Same pattern as above — consistent setup

Reuse of getMockObjectsAdapter() and connectionManager is correct.


402-404: Same pattern — correct

Keeps tests coherent with the Helpers changes.

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

62-73: Prefer direct field assignment over reflective setPrivateField; tighten verify matcher

ConnectionManager.objectsGCGracePeriod is now a public field; assigning it directly is clearer and less brittle. Also, use a typed matcher for the listener for clarity.

-    connManager.setPrivateField("objectsGCGracePeriod", 123L)
+    connManager.objectsGCGracePeriod = 123L
@@
-    verify(exactly = 0) { adapter.connection.once(ConnectionEvent.connected, any()) }
+    verify(exactly = 0) { adapter.connection.once(ConnectionEvent.connected, any<ConnectionStateListener>()) }

Note: Per learnings, the server never sends a value < 120_000 ms, so no client-side clamping assertions are needed.


75-92: Solid deferred-path test; use typed matcher for listener

Behavior is correct (defer until connected, then read CM value). Minor nit: prefer typed matcher in verify.

-    verify(exactly = 1) { connection.once(ConnectionEvent.connected, any()) }
+    verify(exactly = 1) { connection.once(ConnectionEvent.connected, any<ConnectionStateListener>()) }

94-108: Covers deferred-with-null; add connected-with-null case and tighten matcher

This verifies null propagation after connect. Consider also covering “already connected, value null” to ensure no listener registration in that path. Tighten matcher type.

-    verify(exactly = 1) { adapter.connection.once(ConnectionEvent.connected, any()) }
+    verify(exactly = 1) { adapter.connection.once(ConnectionEvent.connected, any<ConnectionStateListener>()) }

Additional test suggestion (outside this hunk):

@Test
fun testRetrieveObjectsGCGracePeriodAlreadyConnectedInvokesImmediatelyWithNull() {
  val adapter = getMockObjectsAdapter()
  every { adapter.connection.state } returns io.ably.lib.realtime.ConnectionState.connected
  var value: Long? = 0L // sentinel
  adapter.retrieveObjectsGCGracePeriod { v -> value = v }
  assertNull(value)
  verify(exactly = 0) { adapter.connection.once(ConnectionEvent.connected, any<ConnectionStateListener>()) }
}

291-309: Good negative-path coverage for ensureAttached on non-attached transition; strengthen matcher

Test correctly surfaces the reason and error code when attach does not reach attached. Prefer typed matcher for the channel listener.

-    verify(exactly = 1) { channel.once(any()) }
+    verify(exactly = 1) { channel.once(any<ChannelStateListener>()) }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c6e3303 and 592fa58.

📒 Files selected for processing (2)
  • lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java (2 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/src/test/java/io/ably/lib/test/realtime/RealtimeChannelTest.java
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: sacOO7
PR: ably/ably-java#1158
File: liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt:61-67
Timestamp: 2025-08-28T10:45:52.258Z
Learning: In the Ably LiveObjects system, the server will never send an objectsGCGracePeriod value less than 2 minutes (120000 ms), so client-side validation/clamping of this value is not required.
📚 Learning: 2025-08-07T07:17:33.340Z
Learnt from: sacOO7
PR: ably/ably-java#1137
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:6-6
Timestamp: 2025-08-07T07:17:33.340Z
Learning: In the ably-java LiveObjects test code, there are extension properties defined in TestHelpers.kt that provide access to private fields of classes for testing purposes. For example, `internal var DefaultLiveMap.LiveMapManager: LiveMapManager` allows tests to access the private `liveMapManager` field. These extension imports (like `import io.ably.lib.objects.unit.LiveMapManager`) should not be removed as they are necessary for test functionality and are not conflicting imports.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
📚 Learning: 2025-08-14T10:29:31.544Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt:25-28
Timestamp: 2025-08-14T10:29:31.544Z
Learning: In MockK with relaxed = true, accessing Java fields on mocked objects automatically creates mock instances, so assignments like `mockAdapter.connectionManager.maxMessageSize = value` work correctly without needing to explicitly create and assign the nested mock object.

Applied to files:

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

8-11: Imports correctly align with new realtime event handling

Added imports are necessary for the new connection event-based tests; mockk wildcard in tests is acceptable.


22-23: Good switch to getMockObjectsAdapter() and using adapter.connectionManager

This aligns tests with the new Helpers.kt extensions and avoids stubbing a separate ConnectionManager.


112-113: Consistent setup here as well

Keeps the tests aligned with the new Helpers.kt extensions.


389-390: Valid move to adapter/CM-based setup

Accurately reflects the new connection/CM flow.

@sacOO7 sacOO7 force-pushed the feature/liveobjects-use-server-gc-period branch from 592fa58 to d39929d Compare August 28, 2025 11:35
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 28, 2025 11:36 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 28, 2025 11:38 Inactive
@sacOO7 sacOO7 requested a review from ttypic August 28, 2025 12:14
@github-actions github-actions bot temporarily deployed to staging/pull/1158/features August 29, 2025 11:03 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1158/javadoc August 29, 2025 11:05 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: 2

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

280-290: Also assert we don’t redundantly call attach() while waiting

Strengthen the contract for the “attaching → attached” path by ensuring attach isn’t invoked again while waiting.

Apply this diff:

     adapter.ensureAttached("ch")
     verify(exactly = 1) { channel.once(any<ChannelStateListener>()) }
+    verify(exactly = 0) { channel.attach(any()) }

292-310: Great negative-path coverage; add one more assertion

Good check that a non-attached terminal state surfaces an error including the reason. Also verify no attach attempt is made on this failure path.

Apply this diff:

     val ex = assertFailsWith<AblyException> { adapter.ensureAttached("ch") }
     assertEquals(ErrorCode.ChannelStateError.code, ex.errorInfo.code)
     assertTrue(ex.errorInfo.message.contains("Not attached"))
     verify(exactly = 1) { channel.once(any<ChannelStateListener>()) }
+    verify(exactly = 0) { channel.attach(any()) }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d39929d and 544c079.

📒 Files selected for processing (1)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (8 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: sacOO7
PR: ably/ably-java#1158
File: liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt:61-67
Timestamp: 2025-08-28T10:45:52.258Z
Learning: In the Ably LiveObjects system, the server will never send an objectsGCGracePeriod value less than 2 minutes (120000 ms), so client-side validation/clamping of this value is not required.
📚 Learning: 2025-08-07T07:17:33.340Z
Learnt from: sacOO7
PR: ably/ably-java#1137
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt:6-6
Timestamp: 2025-08-07T07:17:33.340Z
Learning: In the ably-java LiveObjects test code, there are extension properties defined in TestHelpers.kt that provide access to private fields of classes for testing purposes. For example, `internal var DefaultLiveMap.LiveMapManager: LiveMapManager` allows tests to access the private `liveMapManager` field. These extension imports (like `import io.ably.lib.objects.unit.LiveMapManager`) should not be removed as they are necessary for test functionality and are not conflicting imports.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
📚 Learning: 2025-08-14T10:29:31.544Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt:25-28
Timestamp: 2025-08-14T10:29:31.544Z
Learning: In MockK with relaxed = true, accessing Java fields on mocked objects automatically creates mock instances, so assignments like `mockAdapter.connectionManager.maxMessageSize = value` work correctly without needing to explicitly create and assign the nested mock object.

Applied to files:

  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
🧬 Code graph analysis (1)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (3)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getMockObjectsAdapter (49-55)
liveobjects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt (1)
  • setPrivateField (22-26)
liveobjects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (37-37)
⏰ 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). (12)
  • GitHub Check: check
  • GitHub Check: check (29)
  • GitHub Check: build
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check-liveobjects
  • GitHub Check: check-realtime
  • GitHub Check: check-rest
  • GitHub Check: check (24)
  • GitHub Check: check (19)
  • GitHub Check: check (21)
  • GitHub Check: check (29)
🔇 Additional comments (4)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (4)

22-23: Queue flag wiring looks correct

Good use of ClientOptions.queueMessages to drive the second-arg boolean to ConnectionManager.send; verification matches behavior.


44-45: Error propagation test is solid

Asserting AblyException with expected status/code from clientError is correct and guards regression in sendAsync error handling.


391-405: Good coverage of inactive connection rejection

Validates propagation of ConnectionManager.stateErrorInfo when isActive is false. Looks correct and aligns with expected write-path gating.


62-73: Avoid false negatives by stubbing a stable Connection instance

Verifying zero calls on adapter.connection.once risks checking a different mock instance than the one used inside the code under test. Stub adapter.connection to return a fixed mock and verify against that instance.

Apply this diff in this test to stabilize the mock:

   fun testRetrieveObjectsGCGracePeriodImmediateInvokesBlock() {
     val adapter = getMockObjectsAdapter()
     val connManager = adapter.connectionManager
     connManager.setPrivateField("objectsGCGracePeriod", 123L)

     var value: Long? = null
-    adapter.retrieveObjectsGCGracePeriod { v -> value = v }
+    val connection = mockk<io.ably.lib.realtime.Connection>(relaxed = true)
+    every { adapter.connection } returns connection
+    adapter.retrieveObjectsGCGracePeriod { v -> value = v }

     assertEquals(123L, value)
-    verify(exactly = 0) { adapter.connection.once(ConnectionEvent.connected, any<ConnectionStateListener>()) }
+    verify(exactly = 0) { connection.once(ConnectionEvent.connected, any<ConnectionStateListener>()) }
   }

If you prefer importing the type, add:

import io.ably.lib.realtime.Connection
⛔ Skipped due to learnings
Learnt from: sacOO7
PR: ably/ably-java#1148
File: live-objects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt:25-28
Timestamp: 2025-08-14T10:29:31.544Z
Learning: In MockK with relaxed = true, accessing Java fields on mocked objects automatically creates mock instances, so assignments like `mockAdapter.connectionManager.maxMessageSize = value` work correctly without needing to explicitly create and assign the nested mock object.

…ery time

CONNECTED message is received, updated tests for the same
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 544c079 and 9f8263f.

📒 Files selected for processing (3)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt (3 hunks)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (4 hunks)
  • liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
🧰 Additional context used
🧬 Code graph analysis (1)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (3)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/TestHelpers.kt (1)
  • getMockObjectsAdapter (49-55)
liveobjects/src/test/kotlin/io/ably/lib/objects/TestUtils.kt (1)
  • setPrivateField (22-26)
liveobjects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
  • clientError (37-37)
⏰ 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). (12)
  • GitHub Check: check (29)
  • GitHub Check: check-realtime
  • GitHub Check: check-rest
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-liveobjects
  • GitHub Check: check-rest-okhttp
  • GitHub Check: check (29)
  • GitHub Check: check (19)
  • GitHub Check: check (21)
  • GitHub Check: check (24)
  • GitHub Check: check
  • GitHub Check: build
🔇 Additional comments (2)
liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt (2)

77-92: Stabilize the connection mock before stubbing .on(…).

getMockObjectsAdapter() returns a relaxed mock, so repeated adapter.connection calls can yield different mock instances. If the implementation fetches a fresh instance, your stub and verify(exactly = 1) assertion won’t observe the call. Please pin the instance by stubbing adapter.connection to return the locally-declared mock before configuring .on(...).

-    val connection = adapter.connection
+    val connection = mockk<io.ably.lib.realtime.Connection>(relaxed = true)
+    every { adapter.connection } returns connection

95-109: Mirror the connection stub in the null-path test.

Same reasoning as the previous test: without stubbing adapter.connection to return your local mock, the implementation can call adapter.connection and receive a different instance, so the listener wiring and verification become brittle. Pin it to the local mock first.

-    val connection = adapter.connection
+    val connection = mockk<io.ably.lib.realtime.Connection>(relaxed = true)
+    every { adapter.connection } returns connection

Comment on lines +62 to +67
gcPeriodSubscription = realtimeObjects.adapter.onGCGracePeriodUpdated { period ->
period?.let {
gcGracePeriod = it
Log.i(tag, "Using objectsGCGracePeriod from server: $gcGracePeriod ms")
} ?: Log.i(tag, "Server did not provide objectsGCGracePeriod, using default: $gcGracePeriod ms")
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reset the grace period to the documented default when the server omits it

When a later onGCGracePeriodUpdated callback receives null, we continue using whatever server value we last saw. That contradicts the documented behavior (“fall back to this default value”) and can leave us running with a stale (possibly much shorter) grace period after reconnects, risking premature tombstone eviction. The log message also becomes misleading in that case. Please restore gcGracePeriod to the default before logging.

     gcPeriodSubscription = realtimeObjects.adapter.onGCGracePeriodUpdated { period ->
       period?.let {
         gcGracePeriod = it
         Log.i(tag, "Using objectsGCGracePeriod from server: $gcGracePeriod ms")
-      } ?: Log.i(tag, "Server did not provide objectsGCGracePeriod, using default: $gcGracePeriod ms")
+      } ?: run {
+        gcGracePeriod = ObjectsPoolDefaults.GC_GRACE_PERIOD_MS
+        Log.i(tag, "Server did not provide objectsGCGracePeriod, using default: $gcGracePeriod ms")
+      }
     }
🤖 Prompt for AI Agents
In liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt around lines
62-67, the onGCGracePeriodUpdated null branch must reset gcGracePeriod to the
documented default before logging; change the null path to assign the default
grace period (use the existing default constant or the module's documented
default value) to gcGracePeriod and then log that the default is being used so
we don't retain a stale server value or emit a misleading message.

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: use server gc period for removing tombstoned objects

2 participants