Skip to content

Commit ee488e7

Browse files
darkskygitforehalo
andauthored
feat: fix memory leak & improve memory allocation (toeverything#478)
* chore: format codes * feat: add memory leak test * feat: add array ins del test * feat: replace left & right to weak arc * feat: use weak item in ItemBuilder * feat: replace more strong arc * feat: use weak arc in search marker * chore: restore random crdt item test * chore: cleanup codes * perf(jwst-codec): reduce mem alloc times * feat: move out `std::sync::*` to use loom * fix: test * fix: deps lock glitch * fix: crate pointing in macro --------- Co-authored-by: forehalo <[email protected]>
1 parent cf8785b commit ee488e7

File tree

25 files changed

+426
-210
lines changed

25 files changed

+426
-210
lines changed

Cargo.lock

Lines changed: 17 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/jwst-binding/jwst-jni/src/storage.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ impl JwstStorage {
3737

3838
let storage = rt
3939
.block_on(
40-
AutoStorage::new_with_migration(&format!("sqlite:{path}?mode=rwc"), BlobStorageType::DB).or_else(|e| {
40+
AutoStorage::new_with_migration(
41+
&format!("sqlite:{path}?mode=rwc"),
42+
BlobStorageType::DB,
43+
)
44+
.or_else(|e| {
4145
warn!(
4246
"Failed to open storage, falling back to memory storage: {}",
4347
e

libs/jwst-binding/jwst-swift/src/storage.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ impl Storage {
2727

2828
let storage = rt
2929
.block_on(
30-
AutoStorage::new_with_migration(&format!("sqlite:{path}?mode=rwc"), BlobStorageType::DB).or_else(|e| {
30+
AutoStorage::new_with_migration(
31+
&format!("sqlite:{path}?mode=rwc"),
32+
BlobStorageType::DB,
33+
)
34+
.or_else(|e| {
3135
warn!(
3236
"Failed to open storage, falling back to memory storage: {}",
3337
e

libs/jwst-codec-util/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pub mod doc_operation;
22

3-
pub use doc_operation::*;
3+
pub use doc_operation::*;

libs/jwst-codec/Cargo.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ nanoid = "0.4.0"
1414
nom = "7.1.3"
1515
ordered-float = "3.6.0"
1616
rand = "0.8.5"
17+
rand_chacha = "0.3.1"
1718
serde_json = "1.0.94"
1819
thiserror = "1.0.40"
1920

@@ -24,6 +25,9 @@ jwst-logger = { workspace = true }
2425
arbitrary = { version = "1.3.0", features = ["derive"] }
2526
ordered-float = { version = "3.6.0", features = ["arbitrary"] }
2627

28+
[target.'cfg(loom)'.dependencies]
29+
loom = "0.6"
30+
2731
[dev-dependencies]
2832
assert-json-diff = "2.0.2"
2933
criterion = { version = "0.5.1", features = ["html_reports"] }
@@ -32,12 +36,14 @@ ordered-float = { version = "3.6.0", features = ["proptest"] }
3236
path-ext = "0.1.0"
3337
proptest = "1.1.0"
3438
proptest-derive = "0.3.0"
35-
rand = "0.8.5"
36-
rand_chacha = "0.3.1"
3739
serde = { version = "1.0.155", features = ["derive"] }
3840
y-sync = { git = "https://github.com/toeverything/y-sync", rev = "aeb0010" }
3941
yrs = "0.16.5"
4042

43+
[[bin]]
44+
name = "memory_leak_test"
45+
path = "bin/memory_leak_test.rs"
46+
4147
[[bench]]
4248
name = "array_ops_benchmarks"
4349
harness = false
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use jwst_codec::*;
2+
use rand::{Rng, SeedableRng};
3+
use rand_chacha::ChaCha20Rng;
4+
5+
fn run_text_test(seed: u64) {
6+
let doc = Doc::with_client(1);
7+
let mut rand = ChaCha20Rng::seed_from_u64(seed);
8+
let mut text = doc.get_or_create_text("test").unwrap();
9+
text.insert(0, "This is a string with length 32.").unwrap();
10+
11+
let iteration = 20;
12+
let mut len = 32;
13+
14+
for i in 0..iteration {
15+
let mut text = text.clone();
16+
let ins = i % 2 == 0;
17+
let pos = rand.gen_range(0..if ins { text.len() } else { len / 2 });
18+
if ins {
19+
let str = format!("hello {i}");
20+
text.insert(pos, &str).unwrap();
21+
len += str.len() as u64;
22+
} else {
23+
text.remove(pos, 6).unwrap();
24+
len -= 6;
25+
}
26+
}
27+
28+
assert_eq!(text.to_string().len(), len as usize);
29+
assert_eq!(text.len(), len);
30+
}
31+
32+
fn run_array_test(seed: u64) {
33+
let doc = Doc::with_client(1);
34+
let mut rand = ChaCha20Rng::seed_from_u64(seed);
35+
let mut array = doc.get_or_create_array("test").unwrap();
36+
array.push(1).unwrap();
37+
38+
let iteration = 20;
39+
let mut len = 1;
40+
41+
for i in 0..iteration {
42+
let mut array = array.clone();
43+
let ins = i % 2 == 0;
44+
let pos = rand.gen_range(0..if ins { array.len() } else { len / 2 });
45+
if ins {
46+
array.insert(pos, 1).unwrap();
47+
len += 1;
48+
} else {
49+
array.remove(pos, 1).unwrap();
50+
len -= 1;
51+
}
52+
}
53+
54+
assert_eq!(array.len(), len);
55+
}
56+
57+
fn main() {
58+
let mut rand = ChaCha20Rng::seed_from_u64(rand::thread_rng().gen());
59+
for _ in 0..10000 {
60+
let seed = rand.gen();
61+
run_array_test(seed);
62+
run_text_test(seed);
63+
}
64+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ impl AwarenessEventBuilder {
152152
#[cfg(test)]
153153
mod tests {
154154
use super::*;
155-
use std::sync::Mutex;
155+
use crate::sync::{Mutex, MutexGuard};
156156

157157
#[test]
158158
fn test_awareness() {
@@ -219,7 +219,7 @@ mod tests {
219219
awareness.set_local_state("test".to_string());
220220
awareness.clear_local_state();
221221

222-
let values: std::sync::MutexGuard<Vec<AwarenessEvent>> = values.lock().unwrap();
222+
let values: MutexGuard<Vec<AwarenessEvent>> = values.lock().unwrap();
223223
assert_eq!(values.len(), 4);
224224
let event = values.get(0).unwrap();
225225

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

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
use std::sync::{
2-
atomic::{AtomicU8, Ordering},
3-
Arc,
4-
};
5-
61
use super::*;
2+
use crate::sync::{Arc, AtomicU8, Ordering};
73

84
#[derive(Debug, Clone)]
95
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
@@ -116,22 +112,22 @@ impl ItemFlags {
116112
}
117113

118114
#[derive(Clone)]
119-
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
115+
#[cfg_attr(all(test, not(loom)), derive(proptest_derive::Arbitrary))]
120116
pub struct Item {
121117
pub id: Id,
122118
pub origin_left_id: Option<Id>,
123119
pub origin_right_id: Option<Id>,
124-
#[cfg_attr(test, proptest(value = "None"))]
120+
#[cfg_attr(all(test, not(loom)), proptest(value = "None"))]
125121
pub left: Option<StructInfo>,
126-
#[cfg_attr(test, proptest(value = "None"))]
122+
#[cfg_attr(all(test, not(loom)), proptest(value = "None"))]
127123
pub right: Option<StructInfo>,
128124
pub parent: Option<Parent>,
129125
pub parent_sub: Option<String>,
130126
// make content Arc, so we can share the content between items
131127
// and item can be readonly and cloned fast.
132128
// TODO: considering using Cow
133129
pub content: Arc<Content>,
134-
#[cfg_attr(test, proptest(value = "ItemFlags::default()"))]
130+
#[cfg_attr(all(test, not(loom)), proptest(value = "ItemFlags::default()"))]
135131
pub flags: ItemFlags,
136132
}
137133

@@ -190,6 +186,32 @@ impl Default for Item {
190186
}
191187

192188
impl Item {
189+
pub fn new(
190+
id: Id,
191+
content: Content,
192+
left: Option<ItemRef>,
193+
right: Option<ItemRef>,
194+
parent: Option<Parent>,
195+
parent_sub: Option<String>,
196+
) -> Self {
197+
let flags = ItemFlags::from(if content.countable() {
198+
item_flags::ITEM_COUNTABLE
199+
} else {
200+
0
201+
});
202+
203+
Self {
204+
id,
205+
origin_left_id: left.as_ref().map(|l| l.last_id()),
206+
left: left.map(|l| StructInfo::WeakItem(Arc::downgrade(&l))),
207+
origin_right_id: right.as_ref().map(|r| r.id),
208+
right: right.map(|r| StructInfo::WeakItem(Arc::downgrade(&r))),
209+
parent,
210+
parent_sub,
211+
content: Arc::new(content),
212+
flags,
213+
}
214+
}
193215
pub fn is_empty(&self) -> bool {
194216
self.len() == 0
195217
}
@@ -235,15 +257,6 @@ impl Item {
235257
&mut *(Arc::as_ptr(item) as *mut Item)
236258
}
237259

238-
/// Safety:
239-
/// see [inner_mut]
240-
pub(crate) unsafe fn swap(one: &Arc<Item>, other: &Arc<Item>) {
241-
let one = Self::inner_mut(one);
242-
let other = Self::inner_mut(other);
243-
244-
std::mem::swap(one, other);
245-
}
246-
247260
#[allow(dead_code)]
248261
#[cfg(any(debug, test))]
249262
pub(crate) fn print_left(&self) {
@@ -259,6 +272,9 @@ impl Item {
259272
StructInfo::Item(item) => {
260273
ret.push(format!("{item}"));
261274
}
275+
StructInfo::WeakItem(item) => {
276+
ret.push(format!("{:?}", item.upgrade()));
277+
}
262278
StructInfo::GC { id, len } => {
263279
ret.push(format!("GC{id}: {len}"));
264280
break;
@@ -289,6 +305,9 @@ impl Item {
289305
StructInfo::Item(item) => {
290306
ret.push(format!("{item}"));
291307
}
308+
StructInfo::WeakItem(item) => {
309+
ret.push(format!("{:?}", item.upgrade()));
310+
}
292311
StructInfo::GC { id, len } => {
293312
ret.push(format!("GC{id}: {len}"));
294313
break;
@@ -354,7 +373,9 @@ impl Item {
354373
debug_assert_ne!(first_5_bit, 10);
355374
Arc::new(Content::read(decoder, first_5_bit)?)
356375
},
357-
..Default::default()
376+
left: None,
377+
right: None,
378+
flags: ItemFlags::from(0),
358379
};
359380

360381
if item.content.countable() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod io;
66
mod item;
77
mod refs;
88
mod update;
9+
#[cfg(any(fuzzing, test))]
910
mod utils;
1011

1112
pub use any::Any;
@@ -16,7 +17,6 @@ pub use io::{CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, RawDecoder, RawEncoder
1617
pub use item::{item_flags, Item, ItemFlags, ItemRef, Parent};
1718
pub use refs::StructInfo;
1819
pub use update::Update;
19-
pub use utils::ItemBuilder;
2020
#[cfg(any(fuzzing, test))]
2121
pub use utils::*;
2222

0 commit comments

Comments
 (0)