fix: don't hang when remapping nested containers w same ID #911
+31
−1
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.
This PR fixes an issue when trying to fork a doc, using the same PeerID, then applyDiff back to the original doc.
In an initial test, this is what I observed on the JS side:
The Root Cause: The infinite loop in
_apply_diffoccurs becausecontainer_remapcontains a self-referential entry (ID -> ID). The resolution logicwhile let Some(rid) = container_remap.get(&id)assumes that remapping implies a change to a different ID. Whenrid == id, this assumption is violated, causing the loop to spin forever.Why
old_id == new_idhappens: In the "fork and merge" scenario with the same PeerID, both the source document (doc2) and the target document (doc1) start from the same state (same PeerID, same Counter). Whendoc2creates a container, it assigns it IDX. Whendoc1applies the diff and executes the same creation operation, it deterministically generates the exact same IDX. This is expected behavior for CRDTs and deterministic systems.Why skipping the insert is correct: The purpose of
container_remapis to translate IDs from the "diff space" to the "local space".old_id != new_id, we need a translation so that subsequent operations in the diff referring toold_idare applied tonew_idlocally.old_id == new_id, the ID in the diff is already valid in the local document (because we just created a container with that exact ID). Therefore, no translation is needed. The identity mapping is implicit.container_remap, we ensure thatcontainer_remap.get(&id)returnsNone, the loop terminates immediately, and the code proceeds to useid(which is correct).Why this level: Fixing it in
handler.rs(where the mapping is created) is better than fixing it inloro.rs(where the mapping is consumed).ID -> ID) from ever entering the data structure._apply_diff(checking for cycles or equality in every iteration).