├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.org ├── src └── lib.rs └── tests └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rculock" 3 | version = "0.1.2" 4 | authors = ["Kevin Liu "] 5 | description = "A synchronization primitive for unlimited lock-free reads with one concurrent writer." 6 | repository = "https://github.com/nivekuil/rculock" 7 | categories = ["concurrency"] 8 | license = "MIT" 9 | 10 | [dependencies] 11 | crossbeam = "0.2" 12 | parking_lot = "0.3" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kevin Liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * rculock 2 | A [[https://en.wikipedia.org/wiki/Read-copy-update][read-copy-update]] lock using [[https://github.com/aturon/crossbeam][crossbeam]]. 3 | 4 | The API is similar to =std::sync::RwLock=. Readers are lock-free and can exist concurrently with a writer, which updates the data protected by the lock only when the write guard is dropped. 5 | 6 | This crate should currently be usable, but isn't a very efficient implementation (see [[https://github.com/nivekuil/rculock/issues/1][#1]]). 7 | 8 | See [[https://docs.rs/rculock/][documentation]] for more details. 9 | 10 | ** Usage 11 | #+BEGIN_SRC toml 12 | [dependencies] 13 | rculock = "0.1" 14 | #+END_SRC 15 | *** Examples 16 | #+BEGIN_SRC rust 17 | use rculock::{RcuLock, RcuGuard}; 18 | 19 | // Create a new RcuLock protecting a piece of data, in this case a number (u32). 20 | let data: RcuLock = RcuLock::new(5); 21 | assert_eq!(5, *data.read()); 22 | { 23 | // The data is cloned and handed to the writer 24 | let mut guard: RcuGuard = data.write(); 25 | // RcuGuard implements `Deref` and `DerefMut` for easy access to the data. 26 | *guard = 4; 27 | // The writer has changed its copy of the data, but the changes 28 | // have not yet made it back to the master `RcuLock`. 29 | assert_eq!(5, *data.read()); 30 | } 31 | // After the write guard is dropped, the state of the resource 32 | // as the writer sees it is atomically stored back into the master RcuLock. 33 | assert_eq!(4, *data.read()); 34 | #+END_SRC 35 | 36 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A lock that allows for an unlimited number of concurrent readers, which are never blocked. 2 | //! Only one writer can access the resource at a time. 3 | //! # Examples 4 | //! ``` 5 | //! use rculock::{RcuLock, RcuGuard}; 6 | //! 7 | //! // Create a new RcuLock protecting a piece of data, in this case a number (u32). 8 | //! let data: RcuLock = RcuLock::new(5); 9 | //! assert_eq!(5, *data.read()); 10 | //! { 11 | //! // The data is cloned and handed to the writer 12 | //! let mut guard: RcuGuard = data.write(); 13 | //! // RcuGuard implements `Deref` and `DerefMut` for easy access to the data. 14 | //! *guard = 4; 15 | //! // The writer has changed its copy of the data, but the changes 16 | //! // have not yet made it back to the master `RcuLock`. 17 | //! assert_eq!(5, *data.read()); 18 | //! } 19 | //! // After the write guard is dropped, the state of the resource 20 | //! // as the writer sees it is atomically stored back into the master RcuLock. 21 | //! assert_eq!(4, *data.read()); 22 | //! ``` 23 | 24 | extern crate parking_lot; 25 | extern crate crossbeam; 26 | use std::sync::Arc; 27 | use std::ops::{Deref, DerefMut}; 28 | use crossbeam::sync::ArcCell; 29 | use parking_lot::{Mutex, MutexGuard}; 30 | 31 | #[derive(Debug)] 32 | pub struct RcuLock { 33 | /// The resource protected by the lock, behind an `Atomic` for atomic stores, 34 | /// and an Arc to hand the resource out to readers without fear of memory leaks. 35 | inner: ArcCell, 36 | /// Mutex to ensure at most one writer to prevent a data race, which will occur 37 | /// when multiple writers each acquire a copy of the resource protected by the 38 | /// `RcuLock`, write to it, and then store their individual changes to the master `RcuLock`. 39 | /// Acquired on `write()` and released when `RcuGuard` is dropped. 40 | write_lock: Mutex<()>, 41 | } 42 | 43 | impl RcuLock { 44 | /// Create a new RcuLock. 45 | pub fn new(target: T) -> RcuLock { 46 | let inner = ArcCell::new(Arc::new(target)); 47 | RcuLock { 48 | inner: inner, 49 | write_lock: Mutex::new(()), 50 | } 51 | } 52 | 53 | /// Acquire a read handle to the `RcuLock`. This operation never blocks. 54 | pub fn read(&self) -> Arc { 55 | self.inner.get() 56 | } 57 | 58 | /// Acquire an exclusive write handle to the `RcuLock`, protected by an `RcuGuard`. 59 | /// This operation blocks if another `RcuGuard` is currently alive, i.e. 60 | /// the `RcuLock` has already handed one out to another writer. 61 | /// 62 | /// Clones the data protected by the `RcuLock`, which can be expensive. 63 | pub fn write(&self) -> RcuGuard { 64 | let guard = self.write_lock.lock(); 65 | let data = self.inner.get(); 66 | RcuGuard { 67 | lock: self, 68 | data: (*data).clone(), 69 | _guard: guard, 70 | } 71 | } 72 | } 73 | 74 | pub struct RcuGuard<'a, T: 'a + Clone> { 75 | lock: &'a RcuLock, 76 | data: T, 77 | _guard: MutexGuard<'a, ()>, 78 | } 79 | 80 | impl<'a, T: Clone> DerefMut for RcuGuard<'a, T> { 81 | fn deref_mut(&mut self) -> &mut T { 82 | &mut self.data 83 | } 84 | } 85 | 86 | impl<'a, T: Clone> Deref for RcuGuard<'a, T> { 87 | type Target = T; 88 | fn deref(&self) -> &T { 89 | &self.data 90 | } 91 | } 92 | 93 | /// On drop, atomically store the data back into the owning `RcuLock`. 94 | impl<'a, T: Clone> Drop for RcuGuard<'a, T> { 95 | fn drop(&mut self) { 96 | let data = Arc::new(self.data.clone()); 97 | self.lock.inner.set(data); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/tests.rs: -------------------------------------------------------------------------------- 1 | extern crate rculock; 2 | use std::sync::Arc; 3 | use rculock::{RcuLock, RcuGuard}; 4 | 5 | #[test] 6 | fn test() { 7 | // Create a new RcuLock protecting a piece of data, in this case a number (u32). 8 | let data: RcuLock = RcuLock::new(5); 9 | assert_eq!(5, *data.read()); 10 | { 11 | // The data is cloned and handed to the writer 12 | let mut guard: RcuGuard = data.write(); 13 | // RcuGuard implements `Deref` and `DerefMut` for easy access to the data. 14 | *guard = 4; 15 | // The writer has changed its copy of the data, but the changes 16 | // have not yet made it back to the master `RcuLock`. 17 | assert_eq!(5, *data.read()); 18 | } 19 | // After the write guard is dropped, the state of the resource 20 | // as the writer sees it is atomically stored back into the master RcuLock. 21 | assert_eq!(4, *data.read()); 22 | } 23 | 24 | #[test] 25 | fn threaded() { 26 | use std::thread; 27 | let data = RcuLock::new(5); 28 | let read = data.read().clone(); 29 | let thread1 = { 30 | let data = data.read().clone(); 31 | thread::spawn(move || for _ in 0..1000 { 32 | assert_eq!(5, *data); 33 | thread::sleep_ms(1); 34 | }) 35 | }; 36 | 37 | let thread2 = thread::spawn(move || { 38 | 39 | for i in 1..1001 { 40 | let mut guard = data.write(); 41 | *guard -= 1; 42 | drop(guard); 43 | assert_eq!(5 - i, *data.read()); 44 | thread::sleep_ms(1); 45 | } 46 | 47 | assert_eq!(-995, *data.read()); 48 | }); 49 | 50 | thread1.join().unwrap(); 51 | thread2.join().unwrap(); 52 | 53 | assert_eq!(5, *read); 54 | } 55 | 56 | #[test] 57 | fn hashmap() { 58 | use std::thread; 59 | use std::collections::HashMap; 60 | let map = Arc::new(RcuLock::new(HashMap::::new())); 61 | map.write().insert(1, 1); 62 | if let Some(x) = map.read().get(&1) { 63 | assert_eq!(*x, 1); 64 | } 65 | let t = { 66 | let map = map.clone(); 67 | thread::spawn(move || { 68 | let mut map = map.write(); 69 | for i in 0..1000 { 70 | map.insert(i % 10, i); 71 | thread::sleep_ms(1); 72 | } 73 | }) 74 | }; 75 | 76 | t.join().unwrap(); 77 | let res = map.read(); 78 | let res = res.get(&9); 79 | assert_eq!(Some(&999), res); 80 | } 81 | 82 | #[test] 83 | fn hashmap_race_condition() { 84 | use std::thread; 85 | use std::collections::HashMap; 86 | let map = Arc::new(RcuLock::new(HashMap::::new())); 87 | map.write().insert(1, 1); 88 | if let Some(x) = map.read().get(&1) { 89 | assert_eq!(*x, 1); 90 | } 91 | // t takes longer than t2 to finish but acquires the write lock first 92 | let t = { 93 | let map = map.clone(); 94 | thread::spawn(move || { 95 | let mut map = map.write(); 96 | for i in 0..1000 { 97 | map.insert(i % 10, i); 98 | thread::sleep_ms(2); 99 | } 100 | }) 101 | }; 102 | thread::sleep_ms(100); 103 | let t2 = { 104 | let map = map.clone(); 105 | thread::spawn(move || { 106 | let mut map = map.write(); 107 | for i in 0..1200 { 108 | map.insert(i % 10, i); 109 | thread::sleep_ms(1); 110 | } 111 | }) 112 | }; 113 | 114 | t.join().unwrap(); 115 | t2.join().unwrap(); 116 | let res = map.read(); 117 | let res = res.get(&9); 118 | assert_eq!(Some(&1199), res); 119 | } 120 | --------------------------------------------------------------------------------