+5
CHANGELOG.md
+5
CHANGELOG.md
···
3
3
This file is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
4
4
This project follows semver and every release is checked by cargo-semver-checks.
5
5
6
+
## [0.2.3] - 2025-10-01
7
+
8
+
### Fixed
9
+
- detect when a guard was forgotten to avoid UB
10
+
6
11
## [0.2.2] - 2025-08-09
7
12
8
13
### Changed
+2
-3
Cargo.toml
+2
-3
Cargo.toml
···
1
1
[package]
2
2
name = "simple-left-right"
3
-
version = "0.2.2"
3
+
version = "0.2.3"
4
4
edition = "2021"
5
5
rust-version = "1.82"
6
6
readme = "README.md"
···
10
10
license = "MIT OR Apache-2.0"
11
11
repository = "https://tangled.sh/did:plc:54jgbo4psy24qu2bk4njtpc4/simple-left-right/"
12
12
description = "Lockfree, realtime safe and copy-free Synchronisation"
13
-
exclude = ["known_mutants_regex.txt"]
14
-
# workspace = "../"
13
+
exclude = ["known_mutants_regex.txt", ".tangled", "mutants.out", "mutants.out.old"]
15
14
16
15
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+10
README.md
+10
README.md
···
13
13
as the write locking spins forever.
14
14
15
15
PRs should keep this state as much as possible.
16
+
17
+
## std::mem::forget
18
+
Forgetting a Read/Write guard could lead to UB, so i detect that and panic if it happens. In order to make
19
+
the detection cheap it doesn't differentiate between cases where a forget would lead to UB and cases where
20
+
it doesn't. Just don't forget the guards and it won't panic.
21
+
22
+
## Git
23
+
This project is hosted on [tangled](https://tangled.org/did:plc:54jgbo4psy24qu2bk4njtpc4/simple-left-right/),
24
+
[github](https://github.com/luca3s/simple-left-right) and [codeberg](https://codeberg.org/increasing/simple-left-right).
25
+
You can create issues and PRs on any platform you like.
+20
-8
src/lib.rs
+20
-8
src/lib.rs
···
36
36
#[derive(Debug)]
37
37
pub struct Reader<T> {
38
38
shared: NonNull<Shared<T>>,
39
+
locked: bool,
39
40
/// for drop check
40
41
_own: PhantomData<Shared<T>>,
41
42
}
···
49
50
50
51
/// this function never blocks. (`fetch_update` loop doesn't count)
51
52
pub fn lock(&mut self) -> ReadGuard<'_, T> {
53
+
if self.locked {
54
+
self.locked = false;
55
+
panic!("ReadGuard was forgotten");
56
+
}
57
+
self.locked = true;
52
58
// SAFETY: value just locked
53
59
let value = unsafe { &*self.shared_ref().lock_read().get() };
54
60
ReadGuard {
···
65
71
fn drop(&mut self) {
66
72
// SAFETY: self.shared is valid and not used after this.
67
73
unsafe { Shared::drop(self.shared) };
74
+
assert!(!self.locked, "ReadGuard was forgotten");
68
75
}
69
76
}
70
77
···
74
81
/// Doesn't implement Clone as that would require refcounting to know when to unlock.
75
82
#[derive(Debug)]
76
83
pub struct ReadGuard<'a, T> {
77
-
reader: &'a Reader<T>,
84
+
reader: &'a mut Reader<T>,
78
85
value: &'a T,
79
86
}
80
87
···
100
107
fn drop(&mut self) {
101
108
// release the read lock
102
109
self.reader.shared_ref().release_read_lock();
110
+
self.reader.locked = false;
103
111
}
104
112
}
105
113
···
112
120
write_ptr: Ptr,
113
121
// buffer is pushed at the back and popped at the front.
114
122
op_buffer: VecDeque<O>,
123
+
locked: bool,
115
124
// needed for drop_check
116
125
_own: PhantomData<Shared<T>>,
117
126
}
···
152
161
Reader {
153
162
shared: self.shared,
154
163
_own: PhantomData,
164
+
locked: false,
155
165
}
156
166
})
157
167
}
···
161
171
impl<T: Absorb<O>, O> Writer<T, O> {
162
172
/// doesn't block. Returns None if the Reader has a `ReadGuard` pointing to the old value.
163
173
pub fn try_lock(&mut self) -> Option<WriteGuard<'_, T, O>> {
174
+
if self.locked {
175
+
self.locked = false;
176
+
panic!("WriteGuard was forgotten");
177
+
}
164
178
self.shared_ref()
165
179
.lock_write(self.write_ptr)
166
180
.ok()
167
181
// locking was successful
168
182
.map(|()| {
169
-
// WriteGuard::new(self)
183
+
self.locked = true;
170
184
let mut guard = WriteGuard { writer: self };
171
185
while let Some(operation) = guard.writer.op_buffer.pop_front() {
172
186
guard.get_data_mut().absorb(operation);
···
186
200
write_ptr,
187
201
op_buffer: VecDeque::new(),
188
202
_own: PhantomData,
203
+
locked: false,
189
204
}
190
205
}
191
206
}
···
201
216
write_ptr,
202
217
op_buffer: VecDeque::new(),
203
218
_own: PhantomData,
219
+
locked: false,
204
220
}
205
221
}
206
222
}
···
229
245
fn drop(&mut self) {
230
246
// SAFETY: self.shared is valid and not used after this.
231
247
unsafe { Shared::drop(self.shared) };
248
+
assert!(!self.locked, "WriteGuard was forgotten");
232
249
}
233
250
}
234
251
···
290
307
}
291
308
}
292
309
293
-
// /// SAFETY: behaves like a &mut T and &mut Vec<O>. https://doc.rust-lang.org/stable/std/marker/trait.Sync.html
294
-
// unsafe impl<T: Send, O: Send> Send for WriteGuard<'_, T, O> {}
295
-
296
-
// /// Safety: can only create shared refs to T, not to O. https://doc.rust-lang.org/stable/std/marker/trait.Sync.html
297
-
// unsafe impl<T: Sync, O> Sync for WriteGuard<'_, T, O> {}
298
-
299
310
impl<T, O> Drop for WriteGuard<'_, T, O> {
300
311
fn drop(&mut self) {
301
312
self.writer.swap();
313
+
self.writer.locked = false;
302
314
}
303
315
}
304
316
+2
-1
tests/tests.rs
+2
-1
tests/tests.rs
···
327
327
}
328
328
329
329
#[test]
330
+
#[should_panic]
330
331
fn forget_lock() {
331
332
let mut writer: Writer<i32, CounterAddOp> = Writer::new(0);
332
333
let mut reader = writer.build_reader().unwrap();
333
334
334
335
let write = writer.try_lock().unwrap();
335
336
core::mem::forget(write);
336
-
let write = writer.try_lock().unwrap();
337
+
let _ = writer.try_lock().unwrap();
337
338
338
339
let read = reader.lock();
339
340
core::mem::forget(read);