Skip to content

feat: add Yex.Doc.update_gaps/2 - #279

Draft
osazemeu wants to merge 1 commit into
satoren:mainfrom
osazemeu:pr/update-gaps
Draft

osazemeu wants to merge 1 commit into
satoren:mainfrom
osazemeu:pr/update-gaps

Conversation

@osazemeu

Copy link
Copy Markdown
Contributor

Closes #276.

Since yrs 0.27 (y-crdt/y-crdt#618), a struct whose same-client predecessors are missing, but whose origin, right origin and parent are all present, is integrated behind a Block::Skip instead of being held as pending. It is readable afterwards, but it is not in get_pending_update/1, monitor_update delivers <<0, 0>> for the transaction, and a diff encoded for a peer whose state vector reaches the hole leaves it out. BlockStore.skips is pub(crate), so callers cannot see the hole. y-crdt/y-crdt#673 asks about that upstream, and y-crdt/y-crdt#670 tracks a related retry bug.

Yex.Doc.update_gaps(doc, update_v1) :: {:ok, %{client_id => clock}} | {:error, term()} answers the question before the update is applied. It decodes the update, takes Update::insertions(true) (per-client clock runs, Skip blocks excluded, so a hole inside the update shows as two runs), and walks each client's runs from the doc's clock for that client:

  • Runs the doc already covers are ignored.
  • A run that starts above the clock reached so far reports that clock.

An empty map means applying the update cannot leave a hole. The doc's state vector comes from the open transaction inside Doc.transaction/3, following prune_pending/1.

Why "starts above the doc's clock" is exactly the skip condition and not an approximation: in yrs 0.28 a skip is created in one place, Update::integrate, for a struct with no missing dependency whose clock is above the end of that client's block list. txn.state_vector() reports the start of a client's first skip, which is never above the list end, so every struct that would create a skip starts above the state-vector clock. Within one update, once a struct pends, the rest of that client's structs pend with it, so none can slip in behind. insertions(true) includes deleted and GC'd structs, so a GC range does not look like a hole.

It does not cover dependencies on other clients' structs or delete ranges. Those stay on the pending path, and the docs point to get_pending_update/1, get_pending_ds/1 and prune_pending/1 for them. A doc that already holds a hole reports the hole's first clock for every update at or past it.

Tests

  • The map repro (client 1 sets k, x, y; x withheld): %{1 => 1} on a doc holding only k, %{} once x is applied.
  • The same case applied directly, showing the premise: y is readable, pending is nil, monitor_update gets <<0, 0>>, and a diff for a peer holding k lacks y.
  • Contiguous and already-known updates return %{}.
  • A two-client update where only client 1 has a hole reports only client 1.
  • A merge_updates result with an internal Skip is reported, on an empty doc and on a doc holding k.
  • Deleted and GC'd structs and a delete-only update report nothing.
  • A doc that already holds a hole keeps reporting it.
  • Works inside Doc.transaction/3; garbage bytes return {:error, _}.

cargo fmt --check, cargo clippy -- -D warnings, mix format --check-formatted, mix credo, mix compile --warnings-as-errors and mix test (647 tests) pass with the NIF built from source.

Since yrs 0.27 a struct whose same-client predecessors are missing, but
whose origin, right origin and parent are present, integrates behind a
skip: readable, but absent from pending, from the monitor_update payload
and from diffs past the hole. update_gaps/2 compares the update's
per-client insertion runs with the doc's state vector and returns the
first missing clock for each client that would leave such a hole.
@coderabbitai

coderabbitai Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0.12.0 (yrs 0.28): an update whose same-client predecessor is missing integrates as live content instead of pending

1 participant