Realtime safe, waitfree, concurrency library

Compare changes

Choose any two refs to compare.

Changed files
+39 -12
src
tests
+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
··· 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
··· 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
··· 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
··· 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);