Skip to content

Conversation

@sacOO7
Copy link
Collaborator

@sacOO7 sacOO7 commented Aug 19, 2025

Summary by CodeRabbit

  • New Features
    • Subscribe to object lifecycle events (e.g., deletion) and manage listeners (add/remove/all) on objects, including LiveMap and LiveCounter.
  • Bug Fixes
    • Improved stability to prevent crashes when a null state-change event occurs, with clearer diagnostics.
  • Documentation
    • Updated terminology from “Live Objects” to “Objects” across API docs for clarity.
  • Tests
    • Added integration tests validating lifecycle deletion events and listener notifications.

@coderabbitai
Copy link

coderabbitai bot commented Aug 19, 2025

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between 9af38df and 915f305.

📒 Files selected for processing (10)
  • lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java (2 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt (7 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapChangeCoordinator.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.kt (6 hunks)
 _____________________________________________
< Because, bugs shouldn't outnumber features. >
 ---------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Walkthrough

Introduces object lifecycle event support: adds a public lifecycle API (ObjectLifecycleChange, ObjectLifecycleEvent), wires internal coordination to emit DELETED on tombstone, extends LiveMap/LiveCounter to expose lifecycle subscriptions, updates tests to assert deletion events, and performs minor docs wording, logging, null-safety, and annotation tweaks.

Changes

Cohort / File(s) Summary of changes
Terminology doc updates
lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java, lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java, lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java, lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java, lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java, live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt, live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt, live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt
Javadoc/KDoc wording: replace “live objects” with “objects”; no API/behavior changes.
Public lifecycle API
lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java, lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java
New public interface for lifecycle subscriptions and new enum with DELETED event.
Expose lifecycle on types
lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java, lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java
Interfaces now extend ObjectLifecycleChange (signature surface expanded).
Internal lifecycle coordination
live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt, live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt
Add internal coordinator/emitter; BaseRealtimeObject inherits coordinator and emits DELETED on tombstone.
Emitter tweaks (logs/null-safety)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt, live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt, live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapChangeCoordinator.kt
Safer null handling in ObjectsStateEmitter; refined warning messages in other emitters.
Annotation metadata
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt
Added @NotNull annotation; no logic change.
Tests for lifecycle deletion
live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.kt
Subscribe to DELETED on objects; assert three DELETED events on deletion.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant LiveObject as LiveMap/LiveCounter (public)
  participant Coordinator as ObjectLifecycleCoordinator (internal)
  participant BaseObj as BaseRealtimeObject
  participant Emitter as ObjectLifecycleEmitter
  participant Listener as Client Listener

  rect rgba(230,245,255,0.6)
  note right of Client: Subscribe to lifecycle
  Client->>LiveObject: on(DELETED, listener)
  LiveObject->>Coordinator: register(listener for DELETED)
  Coordinator-->>Client: ObjectsSubscription
  end

  rect rgba(240,255,240,0.6)
  note right of BaseObj: Tombstone triggered
  BaseObj->>BaseObj: tombstone(serialTimestamp)
  BaseObj->>Coordinator: objectLifecycleChanged(Deleted)
  Coordinator->>Emitter: emit(ObjectLifecycleEvent.DELETED)
  Emitter-->>Listener: onLifecycleEvent(DELETED)
  end

  alt Unsubscribe
    Client->>LiveObject: off(listener) / offAll()
    LiveObject->>Coordinator: unregister(...)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Implement object lifecycle event: emit DELETED when an object is tombstoned (#1132, ECO-5479)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Null-safety change in ObjectsStateEmitter.apply: avoid NPE and log warning (live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt, listener invocation path; exact lines not provided) Not related to lifecycle events; modifies state change emission behavior.

Possibly related PRs

Poem

I thump my paw: “Deleted!” I cheer,
Three soft echoes, crisp and clear.
Maps and counters bow goodnight,
Lifecycle lanterns blink their light.
Docs now whisper simply “objects” true—
A tidy warren, fresh and new. 🐇✨

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Add the reviews.auto_title_instructions setting in your project's settings in CodeRabbit to generate a title for your PR based on the changes in the PR with custom instructions.

✨ 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/liveobject-lifecycle-events

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot temporarily deployed to staging/pull/1151/features August 19, 2025 10:08 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1151/javadoc August 19, 2025 10:11 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1151/features August 19, 2025 10:21 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1151/javadoc August 19, 2025 10:23 Inactive
@sacOO7 sacOO7 force-pushed the feature/liveobject-lifecycle-events branch from 530b9e3 to 9af38df Compare August 19, 2025 10:25
@github-actions github-actions bot temporarily deployed to staging/pull/1151/features August 19, 2025 10:26 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1151/javadoc August 19, 2025 10:27 Inactive
@sacOO7 sacOO7 marked this pull request as ready for review August 19, 2025 10:32
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

Caution

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

⚠️ Outside diff range comments (3)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt (3)

61-65: Track last known state to prevent ensureSynced race and enable re-checks

We need a locally cached state to safely handle races in ensureSynced. Add a volatile field to store the last observed state.

 internal abstract class ObjectsStateCoordinator : ObjectsStateChange, HandlesObjectsStateChange {
   private val tag = "ObjectsStateCoordinator"
+  @Volatile
+  private var lastState: ObjectsState = ObjectsState.Initialized
   private val internalObjectStateEmitter = ObjectsStateEmitter()
   // related to RTC10, should have a separate EventEmitter for users of the library
   private val externalObjectStateEmitter = ObjectsStateEmitter()

77-83: Update lastState before emitting events

Set the cached state for both internal correctness and to enable race-free ensureSynced.

 override fun objectsStateChanged(newState: ObjectsState) {
-    objectsStateToEventMap[newState]?.let { objectsStateEvent ->
+    lastState = newState
+    objectsStateToEventMap[newState]?.let { objectsStateEvent ->
       internalObjectStateEmitter.emit(objectsStateEvent)
       externalObjectStateEmitter.emit(objectsStateEvent)
     }
   }

95-96: Dispose both internal and external state listeners to avoid leaks

disposeObjectsStateListeners currently only clears external listeners. Any pending internal once-listeners (e.g., from ensureSynced) could leak if disposal happens before SYNCED.

-  override fun disposeObjectsStateListeners() = offAll()
+  override fun disposeObjectsStateListeners() {
+    // Clear both internal and external listeners
+    internalObjectStateEmitter.off()
+    externalObjectStateEmitter.off()
+  }
🧹 Nitpick comments (10)
live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (2)

7-7: Avoid applying @NotNull to a Unit-returning function (no effect, can confuse tooling)

Kotlin already emits nullability metadata for Java interop. Annotating the function itself (which returns Unit/void) is ineffective and can trip static analyzers. If you intended to document null-safety, prefer relying on Kotlin types (already non-null here) or, if you must, annotate parameters. For this internal API, the cleanest is to drop the method-level @NotNull.

Apply this diff to remove the unused import and the redundant annotation:

-import org.jetbrains.annotations.NotNull
@@
-  @NotNull
   internal fun <T> launchWithCallback(callback: ObjectsCallback<T>, block: suspend () -> T) {

Also applies to: 69-70


96-104: Harden error path: catch exceptions from callback.onError too

You already guard exceptions thrown by onSuccess, but not by onError. A user callback throwing in onError would currently bubble out of the coroutine. Mirror the success-path guard for consistency.

       } catch (throwable: Throwable) {
-        when (throwable) {
-          is AblyException -> { callback.onError(throwable) }
-          else -> {
-            val ex = ablyException("Error executing operation", ErrorCode.BadRequest, cause = throwable)
-            callback.onError(ex)
-          }
-        }
+        try {
+          when (throwable) {
+            is AblyException -> { callback.onError(throwable) }
+            else -> {
+              val ex = ablyException("Error executing operation", ErrorCode.BadRequest, cause = throwable)
+              callback.onError(ex)
+            }
+          }
+        } catch (t: Throwable) {
+          Log.e(tag, "Error occurred while executing callback's onError handler", t)
+        } // catch and don't rethrow error from callback
       }
lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java (1)

41-41: Minor Javadoc grammar: “flag indicating …”

Prefer gerund for parameter descriptions.

Apply this diff:

-     * @param hasObjects flag indicates whether the channel has any associated objects.
+     * @param hasObjects flag indicating whether the channel has any associated objects.
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (1)

42-42: Nit: Capitalize “Ably” in spec note.

Proper noun should be capitalized.

Apply this diff:

-   * @spec RTO3a - Pool storing all ably objects by object ID
+   * @spec RTO3a - Pool storing all Ably objects by object ID
live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.kt (1)

174-176: Good lifecycle coverage; add a waiter for lifecycleEvents to avoid flakes.

Subscriptions and assertions look correct. Since emissions are asynchronous, consider waiting for the expected lifecycle event count before asserting to reduce flakiness.

Apply this diff at the assertion site:

-    // Assert lifecycle events
-    assertEquals(3, lifecycleEvents.size) // Should have received 3 DELETED lifecycle events
+    // Assert lifecycle events
+    assertWaiter { lifecycleEvents.size == 3 } // Wait for all DELETED lifecycle events to arrive
+    assertEquals(3, lifecycleEvents.size) // Should have received 3 DELETED lifecycle events
     lifecycleEvents.forEach { event ->
       assertEquals(ObjectLifecycleEvent.DELETED, event) // All events should be DELETED
     }

Also applies to: 186-188, 207-209, 232-234, 250-254

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

116-120: Correct the error message to show the actual object’s id

Currently prints the incoming id twice; should include this.objectId for clarity.

   internal fun validateObjectId(objectId: String?) {
     if (this.objectId != objectId) {
-      throw objectError("Invalid object: incoming objectId=${objectId}; $objectType objectId=$objectId")
+      throw objectError("Invalid object: incoming objectId=$objectId; $objectType objectId=${this.objectId}")
     }
   }

93-97: Nit: Kotlin style – remove trailing semicolon on return

Semicolons are optional; prefer idiomatic return without semicolon.

     if (isTombstoned) {
       // this object is tombstoned so the operation cannot be applied
-      return;
+      return
     }

140-143: Minor readability: simplify boolean expression

Avoid == true on a nullable boolean; prefer explicit defaulting.

   internal fun isEligibleForGc(): Boolean {
-    val currentTime = System.currentTimeMillis()
-    return isTombstoned && tombstonedAt?.let { currentTime - it >= ObjectsPoolDefaults.GC_GRACE_PERIOD_MS } == true
+    val at = tombstonedAt ?: return false
+    return isTombstoned && (System.currentTimeMillis() - at) >= ObjectsPoolDefaults.GC_GRACE_PERIOD_MS
   }
lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java (2)

60-67: Make Listener lambda-friendly and add null-safety annotation

Mark Listener as a functional interface and annotate the event param as @NotNull for consistency with the rest of the API.

-    interface Listener {
+    @FunctionalInterface
+    interface Listener {
         /**
          * Called when a lifecycle event occurs.
          *
          * @param lifecycleEvent The lifecycle event that occurred
          */
-        void onLifecycleEvent(ObjectLifecycleEvent lifecycleEvent);
+        void onLifecycleEvent(@NotNull ObjectLifecycleEvent lifecycleEvent);
     }

7-18: Doc nit: clarify current scope of lifecycle events

Right now only DELETED is emitted. Optional: either explicitly state that or add a note that more events will follow as the API evolves.

📜 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 2c36548 and 9af38df.

📒 Files selected for processing (19)
  • lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java (2 hunks)
  • lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java (1 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java (2 hunks)
  • lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt (3 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (2 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt (5 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt (1 hunks)
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapChangeCoordinator.kt (1 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.kt (6 hunks)
  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt (2 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: sacOO7
PR: ably/ably-java#1148
File: lib/src/main/java/io/ably/lib/objects/ObjectsHelper.java:0-0
Timestamp: 2025-08-14T10:43:48.159Z
Learning: In the ably-java codebase, some "LiveObjectsPlugin" references in log messages, Javadoc, build files, and workflows are intentionally retained due to ongoing internal technical debate about isolating API naming from product naming, as discussed in LODR-033. These are not considered incomplete refactoring work but deliberate exceptions to the broader effort to eliminate "liveobject" keyword usage.
📚 Learning: 2025-08-14T10:43:57.974Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: lib/src/main/java/io/ably/lib/objects/ObjectsHelper.java:24-25
Timestamp: 2025-08-14T10:43:57.974Z
Learning: In the ably-java LiveObjects renaming effort, log messages may intentionally retain "LiveObjects" terminology even when other parts of the codebase are being renamed to "Objects", due to internal technical debates about where the renaming should be applied. The team makes deliberate decisions about which parts of the codebase to rename versus which to keep as-is.

Applied to files:

  • lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
  • lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java
  • lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java
  • lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt
  • lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java
  • lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt
📚 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:

  • lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java
  • lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java
📚 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, an extension property `State` (capital S) is defined on the `LiveObjects` interface in live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/Utils.kt to provide access to the internal `state` field by casting to `DefaultLiveObjects`. This allows tests to access internal state for verification purposes.

Applied to files:

  • lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt
📚 Learning: 2025-08-14T10:43:48.159Z
Learnt from: sacOO7
PR: ably/ably-java#1148
File: lib/src/main/java/io/ably/lib/objects/ObjectsHelper.java:0-0
Timestamp: 2025-08-14T10:43:48.159Z
Learning: In the ably-java codebase, some "LiveObjectsPlugin" references in log messages, Javadoc, build files, and workflows are intentionally retained due to ongoing internal technical debate about isolating API naming from product naming, as discussed in LODR-033. These are not considered incomplete refactoring work but deliberate exceptions to the broader effort to eliminate "liveobject" keyword usage.

Applied to files:

  • live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
  • lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java
  • lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt
  • lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java
  • lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java
  • live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.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/objects/RealtimeObjects.java
  • lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java
📚 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:

  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt
📚 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:

  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt
  • lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java
  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.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:

  • live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt
  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.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:

  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.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:

  • live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.kt
🧬 Code Graph Analysis (3)
lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java (2)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt (3)
  • on (66-71)
  • off (73-73)
  • offAll (75-75)
live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt (3)
  • on (54-59)
  • off (61-61)
  • offAll (63-63)
live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt (1)
live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt (1)
  • offAll (75-75)
live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt (1)
live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt (3)
  • objectLifecycleChanged (33-47)
  • objectLifecycleChanged (40-40)
  • objectLifecycleChanged (65-69)
⏰ 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). (2)
  • GitHub Check: check-realtime-okhttp
  • GitHub Check: check-realtime
🔇 Additional comments (24)
lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java (1)

8-8: Javadoc wording alignment LGTM

Updated wording from “live object” to “object” reads better and aligns with the broader terminology shifts. No API or behavior changes.

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

46-46: Clarified warning message LGTM

The more specific log (“LiveMapChange listener callback”) improves diagnostics without altering behavior.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt (1)

46-46: Clarified warning message LGTM

Consistent with the LiveMap variant; improves clarity while keeping behavior unchanged.

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

16-16: Javadoc tweak LGTM

Removing “live” here aligns with the general terminology update while keeping API and contracts intact.

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

23-23: Doc wording update aligns with terminology shift.

"managing objects on a channel" reads well and matches the broader rename. No behavioral changes.


28-28: Spec doc tweak looks good.

"Objects pool storing all objects by object ID" is consistent with the rest of the docs.

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

31-34: Docs align with Objects terminology; no functional change.

Wording updates to class and spec comment look good.


60-61: Javadoc phrasing LGTM.

“Gets an object from the pool by object ID.” reads clearly.

live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt (2)

11-11: Doc wording change LGTM.

Updated wording is accurate and consistent with current terminology.


74-74: Doc wording change LGTM.

“hierarchical structure of objects” is clear and consistent.

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

4-4: Doc rename is consistent and clear.

The wording update from “Live Objects” to “Objects” looks good and aligns with the broader rename. No behavioral change.

lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java (2)

9-18: Javadoc rename LGTM.

Clearer phrasing around “Objects synchronization state event.” No API or behavior change.


43-47: Listener Javadoc rename LGTM.

Consistent with the rename; no functional impact.

lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java (1)

18-18: Public API change: LiveMap now extends ObjectLifecycleChange without default methods

  • ObjectLifecycleChange’s on, off, and offAll are abstract, so any existing LiveMap implementer must now provide these methods.
  • A search found no internal classes implementing LiveMap, but external or third-party implementers could still exist—please verify.
  • If LiveMap is intended to remain publicly implementable, add default no-op implementations for these methods in ObjectLifecycleChange. Otherwise, annotate LiveMap as non-extendable (e.g., @DoNotImplement) and bump the version per your compatibility policy.
  • Add a release note highlighting the new lifecycle subscription API and its potential impact on implementers.
live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultRealtimeObjectsTest.kt (1)

9-9: Lifecycle import is appropriate.

Importing ObjectLifecycleEvent matches the new lifecycle subscription API usage below.

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt (3)

23-27: LGTM: explicit state-to-event mapping avoids accidental INITIALIZED emissions

Mapping INITIALIZED to null is correct and prevents spurious public events. Clean and readable.


100-107: LGTM: null-safe listener invocation with clear logging

Safe-call invocation with a helpful warning on null event is good defensive practice. Consistent with ObjectLifecycle emitter.


84-93: Ignore inapplicable suggestion: lastState not defined

The proposed fix references a lastState field that doesn’t exist in ObjectsStateCoordinator, so the diff cannot be applied as-is. Please disregard this review comment.

Likely an incorrect or invalid review comment.

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

30-31: Good integration: BaseRealtimeObject now coordinates lifecycle emissions

Extending ObjectLifecycleCoordinator plugs the base type into the lifecycle event system as intended by this PR.


125-135: Guard DELETED event emission and add coverage tests

The spec requires emitting a single Deleted lifecycle event when an object is tombstoned, even if tombstone() is called multiple times. Please apply the following changes and add a test case to verify exactly one Deleted event per object:

• In live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt:

   internal fun tombstone(serialTimestamp: Long?): ObjectUpdate {
     if (serialTimestamp == null) {
       Log.w(tag, "Tombstoning object $objectId without serial timestamp, using local timestamp instead")
     }
-    isTombstoned = true
-    tombstonedAt = serialTimestamp?: System.currentTimeMillis()
-    // Emit object lifecycle event for deletion
-    objectLifecycleChanged(ObjectLifecycle.Deleted)
+    val firstTimeTombstone = !isTombstoned
+    isTombstoned = true
+    tombstonedAt = serialTimestamp ?: System.currentTimeMillis()
+    // Emit object lifecycle event for deletion (only once)
+    if (firstTimeTombstone) {
+      objectLifecycleChanged(ObjectLifecycle.Deleted)
+    }
     val update = clearData()
     return update
   }

• Add a unit test (e.g. in BaseRealtimeObjectTest.kt) that:

  • Calls tombstone(null) twice on the same object.
  • Asserts that objectLifecycleChanged(ObjectLifecycle.Deleted) is emitted exactly once.

No existing tests cover this scenario—please add one to ensure correct, idempotent behavior.

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

31-33: Ensure JetBrains Annotations Dependency Includes @nonblocking

The codebase already imports and uses @NonBlocking in multiple classes (e.g. RealtimeObjects.java, ObjectsStateChange.java, ObjectLifecycleChange.java, LiveMap.java, LiveCounter.java). If the org.jetbrains:annotations artifact isn’t declared in your build, these annotations will fail to resolve.

Use this snippet to verify your Gradle/Maven configuration:

#!/bin/bash
find . -maxdepth 1 -type f \( -name 'build.gradle*' -o -name 'pom.xml' \) -print \
  | xargs -I {} sh -c '
      echo "==== {} ====" 
      rg -n "org\.jetbrains:annotations" {} \
        || echo "⚠️ Missing JetBrains annotations dependency in {}"
    '

If it reports “Missing JetBrains annotations,” add the dependency:

• Gradle (build.gradle / build.gradle.kts)
implementation("org.jetbrains:annotations:")

• Maven (pom.xml)

org.jetbrains
annotations
‹latest-version›

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

21-25: LGTM: mapping internal lifecycle to public events isolates API surface

Only Deleted maps to a public event; this matches the PR objective (emit DELETED on tombstone) without over-exposing internal transitions.


65-69: LGTM: lifecycle emission logic is minimal and correct

Coordinator maps and emits the event through the emitter; simple and robust.


74-84: LGTM: emitter apply() is null-safe with good diagnostics

Consistent with ObjectsState emitter. Exception handling and logging are appropriate.

@sacOO7 sacOO7 requested a review from ttypic August 19, 2025 11:03
Copy link
Contributor

@ttypic ttypic left a comment

Choose a reason for hiding this comment

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

LGTM

@sacOO7 sacOO7 force-pushed the feature/liveobject-lifecycle-events branch from 9af38df to e6678b0 Compare August 20, 2025 11:44
@github-actions github-actions bot temporarily deployed to staging/pull/1151/features August 20, 2025 11:45 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/1151/javadoc August 20, 2025 11:46 Inactive
- Updated LiveMap and LiveCounter to extend ObjectLifecycleChange interface
- Implemented ObjectLifecycleCoordinator by extending ObjectLifecycleChange
- Updated testObjectDelete with relevant assertions for ObjectLifecycleEvent.DELETED
@sacOO7 sacOO7 force-pushed the feature/liveobject-lifecycle-events branch from e6678b0 to 915f305 Compare August 20, 2025 11:46
@sacOO7 sacOO7 merged commit 6a601bb into main Aug 20, 2025
12 of 13 checks passed
@sacOO7 sacOO7 deleted the feature/liveobject-lifecycle-events branch August 20, 2025 12:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

LiveObject: Implement object lifecycle event

3 participants