diff --git a/.gitignore b/.gitignore index 55c6d99..321eccd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Generated by Cargo # will have compiled files and executables -/target/ +/target # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html @@ -8,11 +8,3 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk - - -#Added by cargo -# -#already existing elements were commented out - -/target -#Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 7e178e8..11e632a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,17 @@ [package] -name = "slock" -version = "0.1.2" authors = ["Brandon Dyer "] -edition = "2018" +categories = ["asynchronous", "concurrency", "memory-management", "rust-patterns"] description = "An async mutex that never deadlocks." +edition = "2021" +keywords = ["mutex", "smart", "lock", "async"] +license = "MIT" +name = "slock" readme = "README.md" repository = "https://github.com/BrokenLamp/slock-rs" -license = "MIT" -keywords = ["mutex", "smart", "lock", "async"] -categories = ["asynchronous", "concurrency", "memory-management", "rust-patterns"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -default = ["futures", "blocking"] -blocking = ["futures"] - -[dependencies.futures] -version = "0.3" -optional = true +version = "0.2.0" -[dependencies.async-std] -version = "1.6" +[dependencies] +tokio = {version = "1.22", features = ["sync", "time", "rt", "macros"]} [dev-dependencies] lazy_static = "1.4" -futures = "0.3" diff --git a/README.md b/README.md index c82cfe0..cdd31ba 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ let lock = Slock::new(5i32); // Change the lock's value lock.set(|v| v + 1).await; -// Get the lock's value +// Get the lock's value if Copy let value = lock.get().await; -// Or if the value doesn't implement copy +// Get the lock's value if Clone let value = lock.get_clone().await; assert_eq!(value, 6); @@ -25,24 +25,22 @@ assert_eq!(value, 6); It's also possible to extract only the data you need from larger structures without the need to clone the entire thing. ```rust -// A user struct that doesn't implement copy struct User { name: String, age: i32, - // ... lots of other things } let user = Slock::new(User { name: "Bob", age: 32, - // ... lots of other things }); -// Get just the name -// This performs a clone on only the name +// Extract something that is Copy +let age = user.map(|v| v.age).await; + +// Extract something that is Clone let name = user.map(|v| v.name.clone()).await; -// Get just the age -// Extracts only the age, leaving everything else untouched -let age = user.map(|v| v.age).await; +// Increment `age` by 1 +user.set(|v| v.age += 1).await; ``` diff --git a/src/lib.rs b/src/lib.rs index a510249..a26832d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! println!("{}", value); // 6 //! //! // Use in multiple threads -//! futures::join!( +//! tokio::join!( //! do_something_in_a_thread(lock.split()), //! do_something_else_in_a_thread(lock.split()), //! do_another_thing_in_a_thread(lock.split()), @@ -33,7 +33,7 @@ //! ### Don't access a Slock from within another //! //! Bad: -//! ```rust +//! ```rust,ignore //! # use slock::*; //! # use futures::executor::block_on; //! # async { @@ -58,12 +58,11 @@ //! # }; //! ``` -pub use async_std::future::{timeout, TimeoutError}; -use std::{ - cmp::Eq, - collections::HashMap, - hash::Hash, - sync::{Arc, RwLock}, +use std::{cmp::Eq, collections::HashMap, hash::Hash, sync::Arc}; + +use tokio::{ + sync::RwLock, + time::{error::Elapsed, timeout}, }; pub struct SlockData { @@ -98,25 +97,22 @@ impl Slock { /// let name = lock.map(|v| v.name).await; /// # }; /// ``` - pub async fn map(&self, mapper: F) -> Result + pub async fn map(&self, mapper: F) -> Result where F: FnOnce(&T) -> U, { - match self.lock.read() { - Ok(v) => { - timeout(std::time::Duration::from_secs(1), async { - mapper(&v.value) - }) - .await - } - Err(_) => panic!("Slock could not read for map!"), - } + let v = self.lock.read().await; + timeout(std::time::Duration::from_secs(1), async { + mapper(&v.value) + }) + .await } /// A setter for changing the internal data of the lock. /// ```rust /// # use slock::*; - /// # let lock = Slock::new(1i32); + /// let lock = Slock::new(1i32); + /// /// # async { /// lock.set(|v| v + 1).await; /// lock.set(|_| 6).await; @@ -126,27 +122,22 @@ impl Slock { where F: FnOnce(T) -> T, { - match self.lock.write() { - Ok(mut data) => { - let ptr = &mut data.value as *mut T; - unsafe { - let new = timeout(std::time::Duration::from_secs(1), async { - setter(ptr.read()) - }) - .await; - if let Ok(new) = new { - timeout(std::time::Duration::from_secs(1), async { - data.hook.as_mut().map(|hook| hook(&new)); - }) - .await - .ok(); - ptr.write(new); - } - } - data.version += 1; - } - Err(_) => panic!("Slock could not write!"), + let mut data = self.lock.write().await; + let ptr = &mut data.value as *mut T; + let new = timeout(std::time::Duration::from_secs(1), async { + setter(unsafe { ptr.read() }) + }) + .await; + if let Ok(new) = new { + timeout(std::time::Duration::from_secs(1), async { + data.hook.as_mut().map(|hook| hook(&new)); + }) + .await + .ok(); + unsafe { ptr.write(new) }; } + + data.version += 1; } /// Create's a new lock pointing to the same data. @@ -157,6 +148,7 @@ impl Slock { /// let lock = Slock::new(0i32); /// let the_same_lock = lock.split(); /// ``` + #[deprecated = "Use `clone()` instead"] pub fn split(&self) -> Self { Self { lock: self.lock.clone(), @@ -170,15 +162,19 @@ impl Slock { self.lock.clone() } - pub fn hook(&self, hook: F) + pub async fn hook(&self, hook: F) where F: FnMut(&T), { - match self.lock.write() { - Ok(mut data) => { - data.hook = Some(Box::new(hook)); - } - Err(_) => panic!("Slock could not write!"), + let mut data = self.lock.write().await; + data.hook = Some(Box::new(hook)); + } +} + +impl Clone for Slock { + fn clone(&self) -> Self { + Self { + lock: self.lock.clone(), } } } @@ -186,14 +182,12 @@ impl Slock { impl Slock { /// Returns a clone of the lock's data. pub async fn get_clone(&self) -> T { - match self.lock.read() { - Ok(v) => v.value.clone(), - Err(_) => panic!("Slock could not read for clone!"), - } + let data = self.lock.read().await; + data.value.clone() } - /// Creates a clone of the lock and its data. - pub async fn clone_async(&self) -> Self { + /// Create a new lock with data clone from this one. + pub async fn clone_deep(&self) -> Self { return Slock::new(self.get_clone().await); } } @@ -213,7 +207,7 @@ impl Slock> { impl Slock> { /// Converts from `Slock>` to `Slock` pub async fn flatten(&self) -> Slock { - self.map(|inner| inner.split()).await.unwrap() + self.map(|inner| inner.clone()).await.unwrap() } } @@ -246,7 +240,7 @@ impl SlockMap { pub async fn from_key(&self, key: K) -> Option> { self.map(|hash_map| { let key = key; - hash_map.get(&key).map(|inner| inner.split()) + hash_map.get(&key).map(|inner| inner.clone()) }) .await .unwrap() @@ -256,14 +250,12 @@ impl SlockMap { impl Slock { /// If a lock's data implements copy, this will return an owned copy of it. pub async fn get(&self) -> T { - match self.lock.read() { - Ok(v) => v.value, - Err(_) => panic!("Slock could not read for clone!"), - } + let data = self.lock.read().await; + data.value } } pub mod blocking; -unsafe impl Send for Slock {} -unsafe impl Sync for Slock {} +unsafe impl Send for Slock {} +unsafe impl Sync for Slock {} diff --git a/tests/hooks.rs b/tests/hooks.rs index 3a41d48..49badb2 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -1,15 +1,15 @@ -use futures::executor::block_on; use slock::*; -#[test] -fn basic_hooks() { +#[tokio::test] +async fn basic_hooks() { + // SAFETY: Required to increment the static counter unsafe { let lock = Slock::new(()); static mut COUNT: i32 = 0; - lock.hook(|_| COUNT += 1); - block_on(lock.set(|_| ())); - block_on(lock.set(|_| ())); - block_on(lock.set(|_| ())); + lock.hook(|_| COUNT += 1).await; + lock.set(|_| ()).await; + lock.set(|_| ()).await; + lock.set(|_| ()).await; assert_eq!(COUNT, 3); } } diff --git a/tests/tests.rs b/tests/tests.rs index 81e407a..4dd7d83 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,74 +1,74 @@ -use futures::executor::block_on; use lazy_static::lazy_static; use slock::*; use std::sync::atomic::{AtomicU8, Ordering}; -#[test] -fn synchronous() { +#[tokio::test] +async fn synchronous() { let lock = Slock::new(5); - block_on(lock.set(|v| v + 1)); - let new_value = block_on(lock.get_clone()); + lock.set(|v| v + 1).await; + let new_value = lock.get_clone().await; assert_eq!(new_value, 6); } /// Many mutations should be able to happen at the same time without loss. -#[test] -fn asynchronous() { +#[tokio::test] +async fn asynchronous() { let lock = Slock::new(0i32); - block_on(async { + async { let f_1 = lock.set(|v| v + 1); let f_2 = lock.set(|v| v + 1); let f_3 = lock.set(|v| v + 1); let f_4 = lock.set(|v| v + 1); let f_5 = lock.set(|v| v + 1); - futures::join!(f_1, f_2, f_3, f_4, f_5); - }); + tokio::join!(f_1, f_2, f_3, f_4, f_5); + } + .await; - assert_eq!(block_on(lock.get_clone()), 5); + assert_eq!(lock.get().await, 5); } /// Should be able to create multiple references to the same lock. -#[test] -fn reference_counting() { +#[tokio::test] +async fn reference_counting() { let lock_1 = Slock::new(0); - let lock_2 = lock_1.split(); - block_on(lock_1.set(|_| 1)); - assert_eq!(block_on(lock_1.get_clone()), 1); - assert_eq!(block_on(lock_2.get_clone()), 1); + let lock_2 = lock_1.clone(); + lock_1.set(|_| 1).await; + assert_eq!(lock_1.get().await, 1); + assert_eq!(lock_2.get().await, 1); } -#[test] -fn mapping() { +#[tokio::test] +async fn mapping() { struct User { name: &'static str, age: i32, - }; + } let lock = Slock::new(User { name: "Bob", age: 32, }); - let name = block_on(lock.map(|v| v.name)).unwrap(); - let age = block_on(lock.map(|v| v.age)).unwrap(); + let name = lock.map(|v| v.name).await.unwrap(); + let age = lock.map(|v| v.age).await.unwrap(); assert_eq!(name, "Bob"); assert_eq!(age, 32); } /// A slock containing a vector should be able to asynchronously push. -#[test] -fn vector() { +#[tokio::test] +async fn vector() { let vec: Vec = Vec::new(); let lock = Slock::new(vec); - block_on(lock.push(1)); - assert_eq!(block_on(lock.map(|v| v[0])).unwrap(), 1); + lock.push(1).await; + assert_eq!(lock.map(|v| v[0]).await.unwrap(), 1); } /// Old value should Drop when a new value is created. -#[test] -fn destruction() { +#[tokio::test] +async fn destruction() { lazy_static! { static ref COUNT: AtomicU8 = AtomicU8::new(0); } @@ -82,15 +82,15 @@ fn destruction() { } let lock = Slock::new(Struct); - block_on(lock.set(|_| Struct)); - block_on(lock.set(|_| Struct)); + lock.set(|_| Struct).await; + lock.set(|_| Struct).await; std::mem::drop(lock); assert_eq!(COUNT.load(Ordering::SeqCst), 3); } /// Old value should not Drop when returned back to the lock. -#[test] -fn non_destruction() { +#[tokio::test] +async fn non_destruction() { lazy_static! { static ref COUNT: AtomicU8 = AtomicU8::new(0); } @@ -104,8 +104,8 @@ fn non_destruction() { } let lock = Slock::new(Struct); - block_on(lock.set(|v| v)); - block_on(lock.set(|v| v)); + lock.set(|v| v).await; + lock.set(|v| v).await; std::mem::drop(lock); assert_eq!(COUNT.load(Ordering::SeqCst), 1); }