From 6e226dbd399e5a809ab9ccea02148c1cfc412094 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 19 Jul 2025 23:30:31 +0100 Subject: [PATCH 1/3] refactor: Allow with_lock to return data and take an async closure --- src/core/utils/with_lock.rs | 173 ++++++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 25 deletions(-) diff --git a/src/core/utils/with_lock.rs b/src/core/utils/with_lock.rs index 914749de..91e8e8d1 100644 --- a/src/core/utils/with_lock.rs +++ b/src/core/utils/with_lock.rs @@ -1,89 +1,212 @@ //! Traits for explicitly scoping the lifetime of locks. -use std::sync::{Arc, Mutex}; +use std::{ + future::Future, + sync::{Arc, Mutex}, +}; pub trait WithLock { - /// Acquires a lock and executes the given closure with the locked data. - fn with_lock(&self, f: F) + /// Acquires a lock and executes the given closure with the locked data, + /// returning the result. + fn with_lock(&self, f: F) -> R where - F: FnMut(&mut T); + F: FnMut(&mut T) -> R; } impl WithLock for Mutex { - fn with_lock(&self, mut f: F) + fn with_lock(&self, mut f: F) -> R where - F: FnMut(&mut T), + F: FnMut(&mut T) -> R, { // The locking and unlocking logic is hidden inside this function. let mut data_guard = self.lock().unwrap(); - f(&mut data_guard); + f(&mut data_guard) // Lock is released here when `data_guard` goes out of scope. } } impl WithLock for Arc> { - fn with_lock(&self, mut f: F) + fn with_lock(&self, mut f: F) -> R where - F: FnMut(&mut T), + F: FnMut(&mut T) -> R, { // The locking and unlocking logic is hidden inside this function. let mut data_guard = self.lock().unwrap(); - f(&mut data_guard); + f(&mut data_guard) // Lock is released here when `data_guard` goes out of scope. } } impl WithLock for lock_api::Mutex { - fn with_lock(&self, mut f: F) + fn with_lock(&self, mut f: F) -> Ret where - F: FnMut(&mut T), + F: FnMut(&mut T) -> Ret, { // The locking and unlocking logic is hidden inside this function. let mut data_guard = self.lock(); - f(&mut data_guard); + f(&mut data_guard) // Lock is released here when `data_guard` goes out of scope. } } impl WithLock for Arc> { - fn with_lock(&self, mut f: F) + fn with_lock(&self, mut f: F) -> Ret where - F: FnMut(&mut T), + F: FnMut(&mut T) -> Ret, { // The locking and unlocking logic is hidden inside this function. let mut data_guard = self.lock(); - f(&mut data_guard); + f(&mut data_guard) // Lock is released here when `data_guard` goes out of scope. } } pub trait WithLockAsync { - /// Acquires a lock and executes the given closure with the locked data. - fn with_lock(&self, f: F) -> impl Future + /// Acquires a lock and executes the given closure with the locked data, + /// returning the result. + fn with_lock(&self, f: F) -> impl Future where - F: FnMut(&mut T); + F: FnMut(&mut T) -> R; + + /// Acquires a lock and executes the given async closure with the locked + /// data. + fn with_lock_async(&self, f: F) -> impl std::future::Future + where + F: AsyncFnMut(&mut T) -> R; } impl WithLockAsync for futures::lock::Mutex { - async fn with_lock(&self, mut f: F) + async fn with_lock(&self, mut f: F) -> R where - F: FnMut(&mut T), + F: FnMut(&mut T) -> R, { // The locking and unlocking logic is hidden inside this function. let mut data_guard = self.lock().await; - f(&mut data_guard); + f(&mut data_guard) + // Lock is released here when `data_guard` goes out of scope. + } + + async fn with_lock_async(&self, mut f: F) -> R + where + F: AsyncFnMut(&mut T) -> R, + { + // The locking and unlocking logic is hidden inside this function. + let mut data_guard = self.lock().await; + f(&mut data_guard).await // Lock is released here when `data_guard` goes out of scope. } } impl WithLockAsync for Arc> { - async fn with_lock(&self, mut f: F) + async fn with_lock(&self, mut f: F) -> R where - F: FnMut(&mut T), + F: FnMut(&mut T) -> R, { // The locking and unlocking logic is hidden inside this function. let mut data_guard = self.lock().await; - f(&mut data_guard); + f(&mut data_guard) + // Lock is released here when `data_guard` goes out of scope. + } + + async fn with_lock_async(&self, mut f: F) -> R + where + F: AsyncFnMut(&mut T) -> R, + { + // The locking and unlocking logic is hidden inside this function. + let mut data_guard = self.lock().await; + f(&mut data_guard).await // Lock is released here when `data_guard` goes out of scope. } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_with_lock_return_value() { + let mutex = Mutex::new(5); + let result = mutex.with_lock(|v| { + *v += 1; + *v * 2 + }); + assert_eq!(result, 12); + let value = mutex.lock().unwrap(); + assert_eq!(*value, 6); + } + + #[test] + fn test_with_lock_unit_return() { + let mutex = Mutex::new(10); + mutex.with_lock(|v| { + *v += 2; + }); + let value = mutex.lock().unwrap(); + assert_eq!(*value, 12); + } + + #[test] + fn test_with_lock_arc_mutex() { + let mutex = Arc::new(Mutex::new(1)); + let result = mutex.with_lock(|v| { + *v *= 10; + *v + }); + assert_eq!(result, 10); + assert_eq!(*mutex.lock().unwrap(), 10); + } + + #[tokio::test] + async fn test_with_lock_async_return_value() { + use futures::lock::Mutex as AsyncMutex; + let mutex = AsyncMutex::new(7); + let result = mutex + .with_lock(|v| { + *v += 3; + *v * 2 + }) + .await; + assert_eq!(result, 20); + let value = mutex.lock().await; + assert_eq!(*value, 10); + } + + #[tokio::test] + async fn test_with_lock_async_unit_return() { + use futures::lock::Mutex as AsyncMutex; + let mutex = AsyncMutex::new(100); + mutex + .with_lock(|v| { + *v -= 50; + }) + .await; + let value = mutex.lock().await; + assert_eq!(*value, 50); + } + + #[tokio::test] + async fn test_with_lock_async_closure() { + use futures::lock::Mutex as AsyncMutex; + let mutex = AsyncMutex::new(1); + mutex + .with_lock_async(async |v| { + *v += 9; + }) + .await; + let value = mutex.lock().await; + assert_eq!(*value, 10); + } + + #[tokio::test] + async fn test_with_lock_async_arc_mutex() { + use futures::lock::Mutex as AsyncMutex; + let mutex = Arc::new(AsyncMutex::new(2)); + mutex + .with_lock_async(async |v: &mut i32| { + *v *= 5; + }) + .await; + let value = mutex.lock().await; + assert_eq!(*value, 10); + } +} From 5b1d47fa4d46d25cb00b8e22090c8164bed11ba3 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 19 Jul 2025 23:32:18 +0100 Subject: [PATCH 2/3] feat: Enable hardware-lock-elision and deadlock_detection --- Cargo.lock | 29 +++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 30 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ed9be6d9..5dce9c59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1659,6 +1659,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.2" @@ -3220,10 +3226,13 @@ version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ + "backtrace", "cfg-if", "libc", + "petgraph", "redox_syscall", "smallvec", + "thread-id", "windows-targets 0.52.6", ] @@ -3273,6 +3282,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.9.0", +] + [[package]] name = "phf" version = "0.11.3" @@ -4894,6 +4913,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread-id" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "thread_local" version = "1.1.9" diff --git a/Cargo.toml b/Cargo.toml index 54f7ae82..ab6a9e8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -517,6 +517,7 @@ version = "1.0" [workspace.dependencies.parking_lot] version = "0.12.4" +features = ["hardware-lock-elision", "deadlock_detection"] # TODO: Check if deadlock_detection has a perf impact, if it does only enable with debug_assertions # Use this when extending with_lock::WithLock to parking_lot [workspace.dependencies.lock_api] From f36028b869f95b1a5b8b4afdf0310b205c40fa4b Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 19 Jul 2025 23:32:53 +0100 Subject: [PATCH 3/3] chore: Disable direnv's nix flake interfering with cargo cache --- .envrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.envrc b/.envrc index 952ec2f8..bad73b75 100644 --- a/.envrc +++ b/.envrc @@ -2,6 +2,6 @@ dotenv_if_exists -use flake ".#${DIRENV_DEVSHELL:-default}" +# use flake ".#${DIRENV_DEVSHELL:-default}" PATH_add bin