Skip to content

Commit fd65274

Browse files
authored
fix: search marker indexing (toeverything#454)
* feat: rename `get_item` to `get_node` * feat: add fuzzing for search marker * feat: switch to renamed apis * chore: add comment * fix: update marker changes * test: enable multiple thread test * fix: lint
1 parent 9bf7de5 commit fd65274

File tree

11 files changed

+243
-93
lines changed

11 files changed

+243
-93
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/jwst-codec/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ ordered-float = { version = "3.6.0", features = ["proptest"] }
3131
proptest = "1.1.0"
3232
proptest-derive = "0.3.0"
3333
rand = "0.8.5"
34+
rand_chacha = "0.3.1"
3435
serde = { version = "1.0.155", features = ["derive"] }
3536
y-sync = { git = "https://github.com/toeverything/y-sync", rev = "aeb0010" }
3637
yrs = "0.16.5"

libs/jwst-codec/fuzz/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/jwst-codec/fuzz/Cargo.toml

+9-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ edition = "2021"
88
cargo-fuzz = true
99

1010
[dependencies]
11-
rand = "0.8"
12-
libfuzzer-sys = "0.4"
11+
rand = "0.8.5"
12+
rand_chacha = "0.3.1"
13+
libfuzzer-sys = "0.4.6"
1314
lib0 = "0.16.5"
1415
yrs = "0.16.5"
1516

@@ -43,6 +44,12 @@ path = "fuzz_targets/decode_bytes.rs"
4344
test = false
4445
doc = false
4546

47+
[[bin]]
48+
name = "parallel_insert_text"
49+
path = "fuzz_targets/parallel_insert_text.rs"
50+
test = false
51+
doc = false
52+
4653
[[bin]]
4754
name = "sync_message"
4855
path = "fuzz_targets/sync_message.rs"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#![no_main]
2+
3+
use jwst_codec::*;
4+
use libfuzzer_sys::fuzz_target;
5+
use rand::{Rng, SeedableRng};
6+
use rand_chacha::ChaCha20Rng;
7+
use std::sync::{
8+
atomic::{AtomicUsize, Ordering},
9+
Arc,
10+
};
11+
12+
fuzz_target!(|seed: u64| {
13+
println!("seed: {}", seed);
14+
let iteration = 10;
15+
let rand = ChaCha20Rng::seed_from_u64(seed);
16+
let added_len = Arc::new(AtomicUsize::new(0));
17+
let mut handles = Vec::new();
18+
19+
let doc = Doc::default();
20+
let mut text = doc.get_or_crate_text("test").unwrap();
21+
text.insert(0, "This is a string with length 32.").unwrap();
22+
23+
// parallel editing text
24+
{
25+
for i in 0..iteration {
26+
let mut text = text.clone();
27+
let mut rand = rand.clone();
28+
let len = added_len.clone();
29+
handles.push(std::thread::spawn(move || {
30+
let pos = rand.gen_range(0..text.len());
31+
let string = format!("hello {i}");
32+
33+
text.insert(pos, &string).unwrap();
34+
len.fetch_add(string.len(), Ordering::SeqCst);
35+
}));
36+
}
37+
}
38+
39+
// parallel editing doc
40+
{
41+
for i in 0..iteration {
42+
let doc = doc.clone();
43+
let mut rand = rand.clone();
44+
let len = added_len.clone();
45+
handles.push(std::thread::spawn(move || {
46+
let mut text = doc.get_or_crate_text("test").unwrap();
47+
let pos = rand.gen_range(0..text.len());
48+
let string = format!("hello doc{i}");
49+
50+
text.insert(pos, &string).unwrap();
51+
len.fetch_add(string.len(), Ordering::SeqCst);
52+
}));
53+
}
54+
}
55+
56+
for handle in handles {
57+
handle.join().unwrap();
58+
}
59+
60+
assert_eq!(
61+
text.to_string().len(),
62+
32 /* raw length */
63+
+ added_len.load(Ordering::SeqCst) /* parallel text editing: insert(pos, string) */
64+
);
65+
});

libs/jwst-codec/src/doc/codec/item.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ impl Default for Item {
179179
}
180180

181181
impl Item {
182+
pub fn left(&self, store: &DocStore) -> Option<Arc<Item>> {
183+
self.left_id.and_then(|id| store.get_item(id))
184+
}
185+
186+
pub fn right(&self, store: &DocStore) -> Option<Arc<Item>> {
187+
self.right_id.and_then(|id| store.get_item(id))
188+
}
189+
182190
pub fn is_empty(&self) -> bool {
183191
self.len() == 0
184192
}
@@ -211,7 +219,7 @@ impl Item {
211219

212220
pub fn get_parent(&self, store: &DocStore) -> JwstCodecResult<Option<Arc<Item>>> {
213221
let parent = match &self.parent {
214-
Some(Parent::Id(id)) => store.get_item(*id).and_then(|i| i.as_item()),
222+
Some(Parent::Id(id)) => store.get_node(*id).and_then(|i| i.as_item()),
215223
Some(Parent::String(_)) => None,
216224
Some(Parent::Type(ty)) => ty.read().unwrap().start.as_ref().and_then(|i| i.as_item()),
217225
None => None,

libs/jwst-codec/src/doc/store.rs

+22-17
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,16 @@ impl DocStore {
136136
}
137137
}
138138

139-
pub fn get_item<I: Into<Id>>(&self, id: I) -> Option<StructInfo> {
140-
self.get_item_with_idx(id).map(|(item, _)| item)
139+
pub fn get_item<I: Into<Id>>(&self, id: I) -> Option<Arc<Item>> {
140+
self.get_node_with_idx(id)
141+
.and_then(|(item, _)| item.as_item())
141142
}
142143

143-
pub fn get_item_with_idx<I: Into<Id>>(&self, id: I) -> Option<(StructInfo, usize)> {
144+
pub fn get_node<I: Into<Id>>(&self, id: I) -> Option<StructInfo> {
145+
self.get_node_with_idx(id).map(|(item, _)| item)
146+
}
147+
148+
pub fn get_node_with_idx<I: Into<Id>>(&self, id: I) -> Option<(StructInfo, usize)> {
144149
let id = id.into();
145150
if let Some(items) = self.items.read().unwrap().get(&id.client) {
146151
if let Some(index) = Self::get_item_index(items, id.clock) {
@@ -151,7 +156,7 @@ impl DocStore {
151156
None
152157
}
153158

154-
pub fn split_item<I: Into<Id>>(
159+
pub fn split_node<I: Into<Id>>(
155160
&mut self,
156161
id: I,
157162
diff: u64,
@@ -295,7 +300,7 @@ impl DocStore {
295300
let mut o = s.clone();
296301

297302
while let Some(left_id) = o.left_id() {
298-
if let Some(left_struct) = self.get_item(left_id) {
303+
if let Some(left_struct) = self.get_node(left_id) {
299304
if left_struct.is_item() {
300305
o = left_struct;
301306
} else {
@@ -334,7 +339,7 @@ impl DocStore {
334339
// Item { id: (1, 0), content: Content::Type(YText) }
335340
// ^^ Parent::Id((1, 0))
336341
Some(Parent::Id(parent_id)) => {
337-
match self.get_item(*parent_id) {
342+
match self.get_node(*parent_id) {
338343
Some(StructInfo::Item(_)) => match &item.content.as_ref() {
339344
Content::Type(ty) => {
340345
item.parent.replace(Parent::Type(ty.clone()));
@@ -355,11 +360,11 @@ impl DocStore {
355360
}
356361
// no item.parent, borrow left.parent or right.parent
357362
None => {
358-
if let Some(left) = item.left_id.and_then(|left_id| self.get_item(left_id)) {
363+
if let Some(left) = item.left_id.and_then(|left_id| self.get_node(left_id)) {
359364
item.parent = left.parent().cloned();
360365
item.parent_sub = left.parent_sub().cloned();
361366
} else if let Some(right) =
362-
item.right_id.and_then(|right_id| self.get_item(right_id))
367+
item.right_id.and_then(|right_id| self.get_node(right_id))
363368
{
364369
item.parent = right.parent().cloned();
365370
item.parent_sub = right.parent_sub().cloned();
@@ -405,8 +410,8 @@ impl DocStore {
405410
parent_lock.as_deref_mut().unwrap()
406411
};
407412

408-
let mut left = item.left_id.and_then(|left_id| self.get_item(left_id));
409-
let mut right = item.right_id.and_then(|right_id| self.get_item(right_id));
413+
let mut left = item.left_id.and_then(|left_id| self.get_node(left_id));
414+
let mut right = item.right_id.and_then(|right_id| self.get_node(right_id));
410415

411416
let right_is_null_or_has_left = match &right {
412417
None => true,
@@ -423,7 +428,7 @@ impl DocStore {
423428
{
424429
// set the first conflicting item
425430
let mut o = if let Some(left) = left.clone() {
426-
left.right_id().and_then(|right_id| self.get_item(right_id))
431+
left.right_id().and_then(|right_id| self.get_node(right_id))
427432
} else if let Some(parent_sub) = &item.parent_sub {
428433
let o = parent.map.as_ref().and_then(|m| m.get(parent_sub).cloned());
429434
o.as_ref().map(|o| self.get_start_item(o))
@@ -466,7 +471,7 @@ impl DocStore {
466471
break;
467472
}
468473
o = match c.right_id {
469-
Some(right_id) => self.get_item(right_id),
474+
Some(right_id) => self.get_node(right_id),
470475
None => None,
471476
};
472477
}
@@ -894,7 +899,7 @@ mod tests {
894899
};
895900
doc_store.add_item(struct_info.clone()).unwrap();
896901

897-
assert_eq!(doc_store.get_item(Id::new(1, 9)), Some(struct_info));
902+
assert_eq!(doc_store.get_node(Id::new(1, 9)), Some(struct_info));
898903
}
899904

900905
{
@@ -910,13 +915,13 @@ mod tests {
910915
doc_store.add_item(struct_info1).unwrap();
911916
doc_store.add_item(struct_info2.clone()).unwrap();
912917

913-
assert_eq!(doc_store.get_item(Id::new(1, 25)), Some(struct_info2));
918+
assert_eq!(doc_store.get_node(Id::new(1, 25)), Some(struct_info2));
914919
}
915920

916921
{
917922
let doc_store = DocStore::new();
918923

919-
assert_eq!(doc_store.get_item(Id::new(1, 0)), None);
924+
assert_eq!(doc_store.get_node(Id::new(1, 0)), None);
920925
}
921926

922927
{
@@ -932,7 +937,7 @@ mod tests {
932937
doc_store.add_item(struct_info1).unwrap();
933938
doc_store.add_item(struct_info2).unwrap();
934939

935-
assert_eq!(doc_store.get_item(Id::new(1, 35)), None);
940+
assert_eq!(doc_store.get_node(Id::new(1, 35)), None);
936941
}
937942
}
938943

@@ -956,7 +961,7 @@ mod tests {
956961
doc_store.add_item(struct_info1.clone()).unwrap();
957962
doc_store.add_item(struct_info2).unwrap();
958963

959-
let s1 = doc_store.get_item(Id::new(1, 0)).unwrap();
964+
let s1 = doc_store.get_node(Id::new(1, 0)).unwrap();
960965
assert_eq!(s1, struct_info1);
961966
let left = doc_store.split_at_and_get_left((1, 1)).unwrap();
962967
assert_eq!(left.len(), 2); // octo => oc_to

libs/jwst-codec/src/doc/types/list/iterator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl Iterator for ListIterator<'_> {
1313
while let Some(n) = self.next.take() {
1414
self.next = n
1515
.right_id()
16-
.and_then(|right_id| self.store.get_item(right_id));
16+
.and_then(|right_id| self.store.get_node(right_id));
1717

1818
if n.deleted() {
1919
continue;

libs/jwst-codec/src/doc/types/list/mod.rs

+8-15
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,7 @@ pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
6666
}
6767
}
6868

69-
pos.right = item
70-
.right_id
71-
.and_then(|right_id| store.get_item(right_id))
72-
.and_then(|right| right.as_item());
69+
pos.right = item.right(store);
7370
pos.left = Some(item);
7471
} else {
7572
break;
@@ -84,9 +81,8 @@ pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
8481
return Err(JwstCodecError::IndexOutOfBound(index));
8582
}
8683

87-
let inner = self.as_inner().write().unwrap();
88-
if let Some(store) = inner.store.upgrade() {
89-
let mut store = store.write().unwrap();
84+
let mut inner = self.as_inner().write().unwrap();
85+
if let Some(mut store) = inner.store_mut() {
9086
let pos = self.find_pos(&inner, &store, index);
9187
Self::insert_after(inner, &mut store, pos, contents)?;
9288
}
@@ -103,7 +99,7 @@ pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
10399
// insert in between an splitable item.
104100
if pos.offset > 0 {
105101
debug_assert!(pos.left.is_some());
106-
let (_, right) = store.split_item(pos.left.as_ref().unwrap().id, pos.offset)?;
102+
let (_, right) = store.split_node(pos.left.as_ref().unwrap().id, pos.offset)?;
107103
pos.right = right.as_item();
108104
}
109105

@@ -138,10 +134,7 @@ pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
138134
}
139135

140136
let inner = self.as_inner().read().unwrap();
141-
142-
if let Some(store) = inner.store.upgrade() {
143-
let store = store.read().unwrap();
144-
137+
if let Some(store) = inner.store() {
145138
let pos = self.find_pos(&inner, &store, index);
146139

147140
if pos.offset == 0 {
@@ -183,7 +176,7 @@ pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
183176
// delete in between an splitable item.
184177
if pos.offset > 0 {
185178
debug_assert!(pos.left.is_some());
186-
let (_, right) = store.split_item(pos.left.as_ref().unwrap().id, pos.offset)?;
179+
let (_, right) = store.split_node(pos.left.as_ref().unwrap().id, pos.offset)?;
187180
pos.right = right.as_item();
188181
}
189182

@@ -192,15 +185,15 @@ pub(crate) trait ListType: AsInner<Inner = YTypeRef> {
192185
if !item.deleted() {
193186
let content_len = item.len() as i64;
194187
if remaining < content_len {
195-
store.split_item(item.id, remaining as u64)?;
188+
store.split_node(item.id, remaining as u64)?;
196189
}
197190
remaining -= content_len;
198191
store.delete_item(&item, Some(&mut lock));
199192
}
200193

201194
pos.right = item
202195
.right_id
203-
.and_then(|right_id| store.get_item(right_id))
196+
.and_then(|right_id| store.get_node(right_id))
204197
.and_then(|right| right.as_item());
205198
pos.left = Some(item);
206199
} else {

0 commit comments

Comments
 (0)