-
Notifications
You must be signed in to change notification settings - Fork 42
[ECO-5076][LiveObjects] Implement getters for objects, map and counter #1138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Added ObjectsStateEvent enum for SYNCING and SYNCED states - Created ObjectsStateChange interface with listener pattern for state changes - Implemented ObjectsSubscription interface for subscription management - Added ObjectsState enum and coordination system with internal/external event emitters - Created HandlesObjectsStateChange interface for state management coordination
- Added getChannelModes and getChannelState methods to LiveObjectsAdapter interface - Implemented channel mode retrieval with fallback from channel options (RTO2a, RTO2b) - Enhanced LiveObjectsPlugin interface with disposal lifecycle documentation - Fixed DefaultLiveObjectsPlugin to use clientError for proper exception handling - Updated Adapter class with channel state and mode management functionality
…h lifecycle tied to given objects instance - Added channel mode and state validation error codes (ChannelModeRequired, ChannelStateError) - Implemented throwIfInvalidAccessApiConfiguration with channel mode validation (RTO2) - Created ObjectsAsyncScope for channel-specific async operations with proper error handling - Added launchWithCallback and launchWithVoidCallback methods for safe async execution - Enhanced validation helpers for channel state and mode requirements
…andle exceptions - Updated ObjectsManager to extend ObjectsStateCoordinator for state management - Fixed state enum references from SYNCED/SYNCING to Synced/Syncing - Added stateChange method with proper state transition logging (RTO2) - Integrated state change event emission through ObjectsStateCoordinator - Enhanced disposal process to include state listener cleanup - Removed unused import from BaseLiveObject
- Updated LiveCounter#data type to AtomicLong to make it thread safe - Replaced generic Callback with ObjectsCallback in async methods - Implemented value() method with channel configuration validation - Added proper adapter validation using throwIfInvalidAccessApiConfiguration - Maintained thread-safe access to counter data through AtomicReference
…ctor param - Added channel state checks for detached and failed - Implemented LiveMap accessor methods: get, entries, keys, values and size - Updated LiveMap data field to be ConcurrentMap for thread safety - Added proper channel configuration validation for all accessor methods - Implemented sequence-based iteration for entries, keys, and values with tombstone filtering - Replaced generic Callback with ObjectsCallback in async methods
- Added getChannelModes and getChannelState methods to adapter - Implemented getRoot method with blocking and async variants using ObjectsAsyncScope - Moved ObjectsState enum to separate file and updated state references - Added proper disposal handling with AblyException instead of string reason - Enhanced async scope management for callback operations - Updated all async method signatures to use ObjectsCallback instead of Callback
- Added getOptions() method to ChannelBase for accessing channel options - Initialize LiveObjects instance on channel creation to process sync messages - Enhanced channel integration with LiveObjects plugin lifecycle management - Fixed channel mode fallback logic by exposing channel options
…ionality - Added UtilsTest with 283 lines of comprehensive utility function tests - Enhanced DefaultLiveObjectsTest with updated state management tests - Updated ObjectsManagerTest with improved sync sequence and state handling tests - Created test coverage for ObjectId validation, state transitions, and error handling - Added tests for ObjectsAsyncScope callback execution and error scenarios
…und async ops - Enhanced IntegrationTest setup with improved test configuration - Updated Sandbox configuration for integration test environment - Improved test infrastructure for Live Objects integration testing - Added better error handling and setup validation for integration tests
- Created PayloadBuilder for constructing test payloads with 135 lines of functionality - Added RestObjects helper with 119 lines for REST API integration testing - Implemented CounterFixtures with test data for LiveCounter integration tests - Created DataFixtures with 84 lines of common test data structures - Added MapFixtures with 157 lines for LiveMap integration test scenarios - Enhanced integration test utilities with comprehensive helper functions
…ad of checking type at runtime - Added DefaultLiveCounterTest with 205 lines of comprehensive integration tests - Created DefaultLiveMapTest with 213 lines covering LiveMap integration scenarios - Implemented test coverage for LiveCounter and LiveMap accessor methods - Added integration tests for object state validation and error handling - Enhanced test suite with real-time synchronization and state change validation
- Added ObjectsCallback interface replacing generic Callback for Live Objects operations - Updated LiveObjects interface to extend ObjectsStateChange and use ObjectsCallback - Refactored LiveCounter and LiveMap async methods to use ObjectsCallback instead of Callback - Added comprehensive Javadoc for ObjectsCallback with operation-specific guidance
WalkthroughThis update introduces and implements core LiveObjects API features, including the Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant LiveObjects
participant ObjectsManager
participant ObjectsAsyncScope
participant Callback
User->>LiveObjects: getRootAsync(callback)
LiveObjects->>ObjectsAsyncScope: launchWithCallback(callback, suspend getRootAsync)
ObjectsAsyncScope->>LiveObjects: suspend getRootAsync()
LiveObjects->>ObjectsManager: ensureSynced()
ObjectsManager-->>LiveObjects: (returns when synced)
LiveObjects-->>ObjectsAsyncScope: return root LiveMap
ObjectsAsyncScope->>Callback: onSuccess(root LiveMap)
sequenceDiagram
participant User
participant LiveMap
participant LiveObjectsAdapter
User->>LiveMap: get(key)
LiveMap->>LiveObjectsAdapter: throwIfInvalidAccessApiConfiguration(channelName)
LiveObjectsAdapter-->>LiveMap: (returns if valid)
LiveMap-->>User: value or null
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
72a8ebb to
b5d0b45
Compare
…alue union type - Added DefaultLiveObjectsTest with 187 lines of comprehensive Live Objects integration tests - Implemented complete integration test suite covering getRoot functionality - Enhanced test coverage for Objects state management and synchronization - Added integration tests for ObjectsCallback interface and async operations - Completed comprehensive test coverage for all Live Objects core functionality
b5d0b45 to
b1c9e47
Compare
0476f23 to
34449c7
Compare
There was a problem hiding this 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
🔭 Outside diff range comments (1)
lib/src/main/java/io/ably/lib/objects/LiveCounter.java (1)
60-60: Fix inconsistent return typeThe
value()method returnsDoublebut the JavaDoc on line 56 states it returns "the current value of the counter as a Long". Counter values are typically integers, so this should beLongfor consistency.- Double value(); + Long value();
♻️ Duplicate comments (5)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (1)
62-63: Same observation as above on pollingPolling for
ObjectsState.Syncedrepeats the potential flakiness; the suggestion above applies here as well.live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt (4)
75-77: Keep assertion messages in sync with enum renamesSame wording mismatch (
SYNCEDvsSynced) – adjust to reflect the new enum naming for consistency.
100-103: Direct state mutations—same concern as in DefaultLiveObjectsTestAssigning
defaultLiveObjects.state = ObjectsState.Syncedsidesteps production pathways. Provide a helper that triggers the real transition to guard against future logic changes.
169-170: Assertion message mismatch for enum renameUpdate “INITIALIZED” → “Initialized” here as well.
180-183: State mutation concern repeatedThe same recommendation about avoiding direct state assignment applies.
🧹 Nitpick comments (13)
live-objects/src/test/kotlin/io/ably/lib/objects/unit/RealtimeObjectsTest.kt (1)
7-14: Rename the test for brevity and intent clarity
testChannelObjectGetterTestrepeats the word Test and does not convey the behaviour being asserted. A concise name such aschannelObjectsGetter_returnsInstance()improves readability and keeps CI reports succinct.- fun testChannelObjectGetterTest() = runTest { + fun channelObjectsGetter_returnsInstance() = runTest {lib/src/main/java/io/ably/lib/objects/LiveObjectsPlugin.java (2)
49-53: Align Javadoc with public API namesThe note references
ablyRealtimeClient.channels.release(channelName)but the public class isAblyRealtime. Consider using the exact type name (AblyRealtime) to avoid confusion for API consumers and future searches.
57-59: Clarify disposal semanticsThe comment says “AblyClient has been closed using client.close()”. The public class is
AblyRealtime, not AblyClient. Updating this wording keeps the documentation consistent and discoverable.live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultLiveObjectsTest.kt (1)
37-38: Potential flakiness due to busy-wait polling
assertWaiter { defaultLiveObjects.state == ObjectsState.Syncing }relies on polling every 100 ms for up to 10 s.
If the coroutine advancing the state is slow under heavy CI load the test may still pass but with a long delay, increasing build time.
Consider emitting aMutableStateFlowor using aCountDownLatchso the test can suspend until the exact signal is emitted instead of polling.live-objects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt (1)
20-22: Message text still refers to legacy constant namesThe assertion message says “state should be INITIALIZED” but the enum name is now
Initialized. Updating messages avoids confusion when reading test failures.- "Initial state should be INITIALIZED" + "Initial state should be Initialized"live-objects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt (2)
65-72: Consider direct implementation for keys() method.The current implementation delegates to
entries()which may be less efficient since it resolves all values just to extract keys.Consider a more direct implementation:
override fun keys(): Iterable<String> { - val iterableEntries = entries() - return sequence { - for (entry in iterableEntries) { - yield(entry.key) // RTLM12b - } - }.asIterable() + adapter.throwIfInvalidAccessApiConfiguration(channelName) + return sequence { + for ((key, entry) in data.entries) { + if (!entry.isEntryOrRefTombstoned(objectsPool)) { + yield(key) + } + } + }.asIterable() }
74-81: Consider direct implementation for values() method.Similar to
keys(), this could be more efficient with direct implementation rather than delegating toentries().Consider a more direct implementation:
override fun values(): Iterable<Any> { - val iterableEntries = entries() - return sequence { - for (entry in iterableEntries) { - yield(entry.value) // RTLM13b - } - }.asIterable() + adapter.throwIfInvalidAccessApiConfiguration(channelName) + return sequence { + for ((_, entry) in data.entries) { + val value = entry.getResolvedValue(objectsPool) + value?.let { yield(it) } + } + }.asIterable() }live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt (2)
3-5: Remove duplicate importsLines 4-5 contain redundant imports that are already covered by the wildcard import on line 3.
import io.ably.lib.objects.* -import io.ably.lib.objects.ObjectData -import io.ably.lib.objects.ObjectValue import io.ably.lib.objects.integration.helpers.fixtures.createUserMapObject
205-205: Use consistent Long literal for size comparisonFor consistency with other size assertions in the test, use
3Linstead of3.- assertEquals(3, testMap.size(), "Final map should have exactly 3 entries") + assertEquals(3L, testMap.size(), "Final map should have exactly 3 entries")live-objects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveObjectsTest.kt (1)
10-10: Remove unused importThe
sizeimport is not used in this file. Thesize()calls in the tests are methods on LiveMap instances, not this imported function.-import io.ably.lib.objects.size import io.ably.lib.objects.state.ObjectsStateEventlive-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt (1)
62-98: Consider extracting duplicated error handling logicThe error handling logic is duplicated between
launchWithCallbackandlaunchWithVoidCallback. Consider extracting it to reduce duplication and improve maintainability.+ private fun handleError(tag: String, callback: ObjectsCallback<*>, throwable: Throwable) { + when (throwable) { + is AblyException -> callback.onError(throwable) + else -> { + val ex = ablyException("Error executing operation", ErrorCode.BadRequest, cause = throwable) + callback.onError(ex) + } + } + } + internal fun <T> launchWithCallback(callback: ObjectsCallback<T>, block: suspend () -> T) { scope.launch { try { val result = block() try { callback.onSuccess(result) } catch (t: Throwable) { Log.e(tag, "Error occurred while executing callback's onSuccess handler", t) } // catch and don't rethrow error from callback } catch (throwable: Throwable) { - when (throwable) { - is AblyException -> { callback.onError(throwable) } - else -> { - val ex = ablyException("Error executing operation", ErrorCode.BadRequest, cause = throwable) - callback.onError(ex) - } - } + handleError(tag, callback, throwable) } } } internal fun launchWithVoidCallback(callback: ObjectsCallback<Void>, block: suspend () -> Unit) { scope.launch { try { block() try { callback.onSuccess(null) } catch (t: Throwable) { Log.e(tag, "Error occurred while executing callback's onSuccess handler", t) } // catch and don't rethrow error from callback } catch (throwable: Throwable) { - when (throwable) { - is AblyException -> { callback.onError(throwable) } - else -> { - val ex = ablyException("Error executing operation", ErrorCode.BadRequest, cause = throwable) - callback.onError(ex) - } - } + handleError(tag, callback, throwable) } } }live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/RestObjects.kt (2)
22-24: Improve readability of null-check with exception.The line break between the Elvis operator and exception makes the code less readable.
- return operationRequest(channelName, mapCreateOp).objectId ?: - throw Exception("Failed to create map: no objectId returned") + return operationRequest(channelName, mapCreateOp).objectId + ?: throw Exception("Failed to create map: no objectId returned")
58-60: Improve readability of null-check with exception.Same formatting issue as above for consistency.
- return operationRequest(channelName, counterCreateOp).objectId - ?: throw Exception("Failed to create counter: no objectId returned") + return operationRequest(channelName, counterCreateOp).objectId + ?: throw Exception("Failed to create counter: no objectId returned")
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Tests
Documentation