You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
import with a reused peer id panics and poisons the doc; dropping it can abort #1118
Hi! This started with a bug on our side: two clients ended up with the same peer id and wrote different ops. We know the docs say not to reuse peer ids. Still, the result was harder to recover from than we expected:
use loro::{ExportMode,LoroDoc};fnmain(){let history = LoroDoc::new();
history.set_peer_id(7).unwrap();
history.get_map("m").insert("k",0).unwrap();
history.get_text("t").insert(0,"a").unwrap();
history.commit();// 7@0, 7@1let other = LoroDoc::new();
other.set_peer_id(7).unwrap();
other.get_text("t").insert(0,"xyz").unwrap();
other.commit();// 7@0..=2let update = other.export(ExportMode::all_updates()).unwrap();let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| history.import(&update)));assert!(r.is_err());// generic-btree lib.rs:618: elem.rle_len=1 but pos.offset=2drop(history);// panics again: poisoned LoroMutex (sync.rs:34)}
The panic comes from RichtextState::apply_diff (in debug builds a debug_assert! in richtext_state.rs fires first). After that:
The doc's mutex is poisoned, so every later call panics, including Drop.
If the doc is dropped while that first panic is unwinding (it's owned inside the catch_unwind closure, or there's no catch_unwind), the second panic happens during cleanup and the process aborts.
Sometimes it doesn't panic at all: when the shifted position happens to be valid, import returns Ok and the text comes out scrambled ("hello world" became "helabclo wordefgld" in one run).
Same on 1.13.9, 1.16.0, 1.16.2 and main (ad5b2a6). #1068 looks like the same poisoned-mutex-in-Drop pattern with a different trigger.
Would it be possible for import to return an Err here, or for Drop not to panic on a poisoned lock, so a failed import can be caught?
Thanks for the fix! A downstream confirmation, with a smaller trigger (the receiver has one map op from peer 1; the update comes from another peer-1 history that inserts and deletes two characters in a text, exported with ExportMode::updates from {1: 1}):
loro = "=1.16.2": richtext_state.rs:2277 (pos: 0, len: 1, self.len(): 0), then poisoned LoroMutex, then the process aborts (SIGABRT), in debug and release builds.
main at 4f60d92: import returns Err(DecodeError("text diff deletes 1 at 0 but state only has 0")).
crates.io still has 1.16.2 as the latest loro. Is a release with #1171 planned? It would let Rust users drop the process isolation they need around import today.
Activity
fix: issue sweep (#1118, #1162, #1155, #1165, #1156, #1164, #1157, #1154
chapdel commentedon Oct 15, 2026
Thanks for the fix! A downstream confirmation, with a smaller trigger (the receiver has one map op from peer 1; the update comes from another peer-1 history that inserts and deletes two characters in a text, exported with
ExportMode::updatesfrom{1: 1}):loro = "=1.16.2":richtext_state.rs:2277(pos: 0, len: 1, self.len(): 0), thenpoisoned LoroMutex, then the process aborts (SIGABRT), in debug and release builds.mainat 4f60d92:importreturnsErr(DecodeError("text diff deletes 1 at 0 but state only has 0")).crates.io still has 1.16.2 as the latest
loro. Is a release with #1171 planned? It would let Rust users drop the process isolation they need aroundimporttoday.