Objects: specify get() sync-wait failure (RTO23c1), malformed sync serial handling (RTO5a6) and related clauses, with UTS unit cases - #514
Open
sacOO7 wants to merge 2 commits into
Conversation
…a1 and RTO27 cross-refs
There was a problem hiding this comment.
Pull request overview
This PR updates the LiveObjects specification to cover several previously unspecified/underspecified edge cases (notably RealtimeObject.get() sync-wait failure modes and malformed OBJECT_SYNC serial handling), and adds corresponding UTS unit-tier coverage to lock in the new behaviors for SDK implementations.
Changes:
- Specify deterministic failure behavior for
get()when the channel entersDETACHED/SUSPENDED/FAILEDduring the RTO23c sync wait (RTO23c1), mirroring existingpublishAndApplybehavior (RTO20e1). - Specify malformed
OBJECT_SYNC.channelSerialhandling (RTO5a6) and an early-success path forpublishAndApplywhen there’s nothing to apply locally (RTO20d4). - Add UTS unit-tier cases for the new spec branches (
get()mid-wait failures; absent/malformedchannelSerialsingle-message sync).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
specifications/objects-features.md |
Adds/adjusts spec clauses for get() mid-wait failure, malformed channelSerial handling, empty synthetic-apply behavior, no-op diff updates, and clarified cleared-state definition. |
uts/objects/unit/realtime_object.md |
Adds unit-tier UTS cases asserting get() fails with 92008 when the channel enters DETACHED/SUSPENDED/FAILED during the sync wait (including cause assertion for FAILED). |
uts/objects/unit/objects_pool.md |
Adds unit-tier UTS cases for single-message sync behavior when channelSerial is absent (RTO5a5) and when it is present-but-malformed and treated as absent (RTO5a6). |
Suppressed comments (1)
specifications/objects-features.md:808
- RTLM22c says to return a no-op update but doesn’t explicitly state how it must be represented. Other no-op cases in this spec require setting
LiveMapUpdate.nooptotrue(e.g. RTLM6e1, RTLM7h, RTLM8g). Please make the no-op marker explicit here too.
- `(RTLM22c)` As an exception to [RTLM22b](#RTLM22b): if the `LiveMapUpdate.update` computed in [RTLM22b](#RTLM22b) contains no changed keys (it is empty), no map key actually changed, so instead of returning an update return a `LiveMapUpdate` marked as a no-op per [RTLO4b4b](#RTLO4b4b)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| - `(RTLC14a1)` `previousData` `Number` - the previous `data` value | ||
| - `(RTLC14a2)` `newData` `Number` - the new `data` value | ||
| - `(RTLC14b)` Return a `LiveCounterUpdate` object with `LiveCounterUpdate.update.amount` set to `newData - previousData` | ||
| - `(RTLC14c)` As an exception to [RTLC14b](#RTLC14b): if `newData` equals `previousData` (that is, the computed delta is `0`), the counter data did not change, so instead of returning an update return a `LiveCounterUpdate` marked as a no-op per [RTLO4b4b](#RTLO4b4b) |
sacOO7
added a commit
to ably/ably-cocoa
that referenced
this pull request
Aug 9, 2026
…and port their UTS unit cases
- RTO23c1: a get() parked waiting for objects sync now fails with its own
error case (getFailedChannelStateChanged — same 92008/400/cause as
publishAndApply's RTO20e1, message stating the object could not be
retrieved), and the stale pending-spec comment cites RTO23c1 directly.
- Port the five new UTS unit cases (RTO23c1 fails-on-channel-{detached,
suspended,failed}, RTO5a5 absent-channel-serial, RTO5a6 malformed-channel-
serial-treated-as-absent) into Test/UTS/unit/objects/, retiring the
superseded native twins.
- Annotate the implementation sites of the newly specified points (RTO20d4,
RTLC14c, RTLM22c) and align the RTO27a1/RTO27 comments with the updated
spec wording.
Spec changes: ably/specification#514
Companion ably-js fix: ably/ably-js#2284
sacOO7
added a commit
to ably/ably-java
that referenced
this pull request
Aug 9, 2026
…nd add their UTS unit tests - RTO23c1: a get() parked waiting for objects sync now fails when the channel enters DETACHED/SUSPENDED/FAILED — ensureSynced routes through the shared pendingSyncWaiters, each waiter carrying a caller-specific failure description (the object could not be retrieved vs RTO20e1's operation could not be applied locally), built into the 92008/400/cause error at the failure site. - RTO5a6: a malformed OBJECT_SYNC channelSerial (no ':' separator) is normalized to null so it takes the same branch as an absent serial (RTO5a5), with a warning logged. - Add the five UTS unit tests derived from the new spec cases (3x RTO23c1 per channel state, RTO5a5, RTO5a6). - Annotate the implementation sites of the newly specified points (RTO20d4, RTLC14c, RTLM22c). Spec changes: ably/specification#514 Companion ably-js fix: ably/ably-js#2284
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
While aligning the LiveObjects implementations in ably-cocoa, ably-java and ably-js against the Objects feature spec, we found several behaviours that the SDKs need but the spec either did not define or defined incompletely:
RealtimeObject.get()could hang forever. RTO23c saysget()waits for the sync state to reachSYNCED, but did not say what happens if the channel leaves a usable state during that wait. Aget()parked while the channel entersFAILED(including via a connection failure whileSUSPENDED), or while the user callsdetach(), waits on a sync that can never complete — the objects data has been cleared per RTO27a and no recovery is possible without user intervention.publishAndApplyalready fails deterministically in the identical wait (RTO20e1);get()had no equivalent clause.OBJECT_SYNCchannelSerialhandling was unspecified. RTO5a1 defines the<sequence id>:<cursor value>format, but no clause said what to do when the serial cannot be parsed. SDK behaviour had diverged.publishAndApplywith an empty synthetic-message list was unspecified. When every serial in thePublishResultisnull(RTO20d1), there is nothing to apply, but the spec still required the RTO20e sync wait.amount: 0counter updates and empty map updates when a diff computes no change.RTO27a1described the cleared state loosely ("zero value") rather than in terms of the existing new-object definitions.Changes
specifications/objects-features.mdRTO23c1(new)get()parked in the RTO23c wait must fail withErrorInfo92008 /statusCode400 /cause= channelerrorReasonwhen the channel entersDETACHED,SUSPENDEDorFAILED— regardless of the state transitioned from (e.g.FAILEDentered fromSUSPENDED). Mirrors RTO20e1,publishAndApply's equivalent failure.RTO5a6(new)channelSerial(no:separator) must be handled as if the serial were absent per RTO5a5 (data applied, sync completes), and a warning should be logged. Treat-as-absent self-heals on the next sync, whereas discarding could leave a clientSYNCINGforever.RTO20d4(new)publishAndApplysuccessfully without performing the RTO20e wait.RTLC14c/RTLM22c(new)0, or a map diff computes no changed keys, return a no-op update per RTLO4b4b instead of a spurious update event.RTO27a1(edited)RTO27preamble (edited)publishAndApply) and RTO23c1 (get).uts/objects/unit/Five new unit-tier test cases, one per behaviour branch:
objects_pool.md:RTO5a5/absent-channel-serial-0(the previously untested baseline the malformed case defers to),RTO5a6/malformed-channel-serial-treated-as-absent-0.realtime_object.md:RTO23c1/fails-on-channel-{detached,suspended,failed}-0— per-state cases following the RTO20e1 precedent; theSUSPENDEDcase is driven through the SDK's internal channel-state handler (as the RTO27 cases do) since a channel-level mock cannot produceSUSPENDED; theFAILEDcase additionally asserts thecause.SDK status
feature/liveobjects-implementation)refactor/uts-objects-unit-into-liveobjects, PR to follow)Notes for reviewers
get()'s mid-wait behaviour stricter than entry (RTO23e/RTL33 tolerates a channel alreadySUSPENDEDat entry, serving retained best-effort data per RTO27b/RTO25b). Rationale: a transition into a non-usable state while parked means the sync being waited on cannot complete as observed (forDETACHED/FAILEDthe data was just cleared); the rejection is recoverable — a retriedget()passes entry and parks anew. This matches the choice RTO20e1 already made, including forSUSPENDED.cause: at the moment the waiter fails, the state-change reason andRealtimeChannel.errorReasonare the same error; on a reason-less transition (e.g. a clean solicited detach) thecauseis absent.