Skip to content

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
mainfrom
objects/get-sync-wait-failure-and-sync-fixes
Open

Objects: specify get() sync-wait failure (RTO23c1), malformed sync serial handling (RTO5a6) and related clauses, with UTS unit cases#514
sacOO7 wants to merge 2 commits into
mainfrom
objects/get-sync-wait-failure-and-sync-fixes

Conversation

@sacOO7

@sacOO7 sacOO7 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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:

  1. RealtimeObject.get() could hang forever. RTO23c says get() waits for the sync state to reach SYNCED, but did not say what happens if the channel leaves a usable state during that wait. A get() parked while the channel enters FAILED (including via a connection failure while SUSPENDED), or while the user calls detach(), waits on a sync that can never complete — the objects data has been cleared per RTO27a and no recovery is possible without user intervention. publishAndApply already fails deterministically in the identical wait (RTO20e1); get() had no equivalent clause.
  2. Malformed OBJECT_SYNC channelSerial handling 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.
  3. publishAndApply with an empty synthetic-message list was unspecified. When every serial in the PublishResult is null (RTO20d1), there is nothing to apply, but the spec still required the RTO20e sync wait.
  4. No-op update events. RTLC14b/RTLM22b as written produce spurious amount: 0 counter updates and empty map updates when a diff computes no change.
  5. RTO27a1 described the cleared state loosely ("zero value") rather than in terms of the existing new-object definitions.

Changes

specifications/objects-features.md

Clause Change
RTO23c1 (new) get() parked in the RTO23c wait must fail with ErrorInfo 92008 / statusCode 400 / cause = channel errorReason when the channel enters DETACHED, SUSPENDED or FAILED — regardless of the state transitioned from (e.g. FAILED entered from SUSPENDED). Mirrors RTO20e1, publishAndApply's equivalent failure.
RTO5a6 (new) A present-but-malformed 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 client SYNCING forever.
RTO20d4 (new) An empty synthetic-message list completes publishAndApply successfully without performing the RTO20e wait.
RTLC14c / RTLM22c (new) When a counter diff computes a delta of 0, or a map diff computes no changed keys, return a no-op update per RTLO4b4b instead of a spurious update event.
RTO27a1 (edited) Cleared object data is now defined as that of a new empty object of its type (RTLM4c / RTLC4b).
RTO27 preamble (edited) Cross-references both in-progress-operation failures: RTO20e1 (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; the SUSPENDED case is driven through the SDK's internal channel-state handler (as the RTO27 cases do) since a channel-level mock cannot produce SUSPENDED; the FAILED case additionally asserts the cause.

SDK status

SDK RTO23c1 RTO5a6 UTS tests
ably-js Implemented in the companion PR: ably/ably-js#2284 Already conformant (treat-as-absent is the js behaviour this clause adopts) In the companion PR
ably-cocoa Implemented (feature/liveobjects-implementation) Implemented Native twins exist; UTS ports to follow
ably-java Implemented (branch refactor/uts-objects-unit-into-liveobjects, PR to follow) Implemented Implemented

Notes for reviewers

  • RTO23c1 deliberately makes get()'s mid-wait behaviour stricter than entry (RTO23e/RTL33 tolerates a channel already SUSPENDED at 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 (for DETACHED/FAILED the data was just cleared); the rejection is recoverable — a retried get() passes entry and parks anew. This matches the choice RTO20e1 already made, including for SUSPENDED.
  • On cause: at the moment the waiter fails, the state-change reason and RealtimeChannel.errorReason are the same error; on a reason-less transition (e.g. a clean solicited detach) the cause is absent.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR 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 enters DETACHED/SUSPENDED/FAILED during the RTO23c sync wait (RTO23c1), mirroring existing publishAndApply behavior (RTO20e1).
  • Specify malformed OBJECT_SYNC.channelSerial handling (RTO5a6) and an early-success path for publishAndApply when there’s nothing to apply locally (RTO20d4).
  • Add UTS unit-tier cases for the new spec branches (get() mid-wait failures; absent/malformed channelSerial single-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.noop to true (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
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.

2 participants