From cb28fb1ff3b5ea71c6fe11956015c7285cb3f3df Mon Sep 17 00:00:00 2001 From: Mica White Date: Tue, 21 May 2024 13:17:38 -0400 Subject: read-lock changes --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 51d8d67..66a4fc0 100644 --- a/README.md +++ b/README.md @@ -73,14 +73,10 @@ println!("{}", *data.1); **Avoid `LockCollection::try_new`.** This constructor will check to make sure that the collection contains no duplicate locks. This is an O(n^2) operation, where n is the number of locks in the collection. `LockCollection::new` and `LockCollection::new_ref` don't need these checks because they use `OwnedLockable`, which is guaranteed to be unique as long as it is accessible. As a last resort, `LockCollection::new_unchecked` doesn't do this check, but is unsafe to call. -**Avoid using distinct lock orders for `LockCollection`.** The problem is that this library must iterate through the list of locks, and not complete until every single one of them is unlocked. This also means that attempting to lock multiple mutexes gives you a lower chance of ever running. Only one needs to be locked for the operation to need a reset. This problem can be prevented by not doing that in your code. Resources should be obtained in the same order on every thread. - -**Avoid tuples in `LockCollection`.** Tuples become spinlocks if the first value is already unlocked. This will be fixed in the future. For now, if you need a tuple, make the lock that is most likely to be locked the first element. +**Avoid using distinct lock orders for `RetryingLockCollection`.** The problem is that this collection must iterate through the list of locks, and not complete until every single one of them is unlocked. This also means that attempting to lock multiple mutexes gives you a lower chance of ever running. Only one needs to be locked for the operation to need a reset. This problem can be prevented by not doing that in your code. Resources should be obtained in the same order on every thread. ## Future Work -Although this library is able to successfully prevent deadlocks, livelocks may still be an issue. Imagine thread 1 gets resource 1, thread 2 gets resource 2, thread 1 realizes it can't get resource 2, thread 2 realizes it can't get resource 1, thread 1 drops resource 1, thread 2 drops resource 2, and then repeat forever. In practice, this situation probably wouldn't last forever. But it would be nice if this could be prevented somehow. A more fair system for getting sets of locks would help, but I have no clue what that looks like. - It might to possible to break the `ThreadKey` system by having two crates import this crate and call `ThreadKey::get`. I'm not quite sure how this works, but Rust could decide to give each crate their own key, ergo one thread would get two keys. I don't think the standard library would have this issue. At a certain point, I have to recognize that someone could also just import the standard library mutex and get a deadlock that way. We should add `Condvar` at some point. I didn't because I've never used it before, and I'm probably not the right person to solve this problem. I think all the synchronization problems could be solved by having `Condvar::wait` take a `ThreadKey` instead of a `MutexGuard`. Something similar can probably be done for `Barrier`. But again, I'm no expert. -- cgit v1.2.3 From c344021797b7e1f8027bd9d1302908f0767e362b Mon Sep 17 00:00:00 2001 From: Mica White Date: Tue, 21 May 2024 13:17:50 -0400 Subject: README change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.md') diff --git a/README.md b/README.md index 66a4fc0..bef7ef5 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ println!("{}", *data.1); **The `ThreadKey` is a mostly-zero cost abstraction.** It doesn't use any memory, and it doesn't really exist at run-time. The only cost comes from calling `ThreadKey::get()`, because the function has to ensure at runtime that the key hasn't already been taken. Dropping the key will also have a small cost. -**Avoid `LockCollection::try_new`.** This constructor will check to make sure that the collection contains no duplicate locks. This is an O(n^2) operation, where n is the number of locks in the collection. `LockCollection::new` and `LockCollection::new_ref` don't need these checks because they use `OwnedLockable`, which is guaranteed to be unique as long as it is accessible. As a last resort, `LockCollection::new_unchecked` doesn't do this check, but is unsafe to call. +**Avoid `LockCollection::try_new`.** This constructor will check to make sure that the collection contains no duplicate locks. This is an O(nlogn) operation, where n is the number of locks in the collection. `LockCollection::new` and `LockCollection::new_ref` don't need these checks because they use `OwnedLockable`, which is guaranteed to be unique as long as it is accessible. As a last resort, `LockCollection::new_unchecked` doesn't do this check, but is unsafe to call. **Avoid using distinct lock orders for `RetryingLockCollection`.** The problem is that this collection must iterate through the list of locks, and not complete until every single one of them is unlocked. This also means that attempting to lock multiple mutexes gives you a lower chance of ever running. Only one needs to be locked for the operation to need a reset. This problem can be prevented by not doing that in your code. Resources should be obtained in the same order on every thread. -- cgit v1.2.3 From 1ed88daa00d478472181f0987112a2b0f2266694 Mon Sep 17 00:00:00 2001 From: Botahamec Date: Wed, 22 May 2024 20:47:28 -0400 Subject: top-level docs --- README.md | 98 ++++++++++++++++++++++++++++++++++++--------------- examples/fibonacci.rs | 29 +++++++++++++++ src/lib.rs | 96 +++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 176 insertions(+), 47 deletions(-) create mode 100644 examples/fibonacci.rs (limited to 'README.md') diff --git a/README.md b/README.md index bef7ef5..01c259e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,16 @@ prevent deadlocks, we need to prevent one of the following: 3. circular wait (The language must enforce that every thread locks mutexes in the exact same order) 4. partial allocation (The language must enforce total allocation) -This library prevents #4, by requiring that all of the resources that a thread needs be locked at once. This is an atomic operation, so either all locks will be acquired, or none will. +This library seeks to solve **partial allocation** by requiring total +allocation. All the resources a thread needs must be allocated at the same +time. In order to request new resources, the old resources must be dropped +first. Requesting multiple resources at once is atomic. You either get all the +requested resources or none at all. + +As an optimization, this library also often prevents **circular wait**. Many +collections sort the locks in order of their memory address. As long as the +locks are always acquired in that order, then time doesn't need to be wasted +on releasing locks after a failure and re-acquiring them later. ## Example @@ -22,16 +31,16 @@ This library prevents #4, by requiring that all of the resources that a thread n let data: Mutex = Mutex::new(0); for _ in 0..N { - thread::spawn(move || { - // each thread gets one thread key - let key = ThreadKey::get().unwrap(); + thread::spawn(move || { + // each thread gets one thread key + let key = ThreadKey::get().unwrap(); - // unlocking a mutex requires a ThreadKey - let mut data = data.lock(key); - *data += 1; + // unlocking a mutex requires a ThreadKey + let mut data = data.lock(key); + *data += 1; - // the key is unlocked at the end of the scope - }); + // the key is unlocked at the end of the scope + }); } let key = ThreadKey::get().unwrap(); @@ -48,16 +57,16 @@ static DATA_1: Mutex = Mutex::new(0); static DATA_2: Mutex = Mutex::new(String::new()); for _ in 0..N { - thread::spawn(move || { - let key = ThreadKey::get().unwrap(); + thread::spawn(move || { + let key = ThreadKey::get().unwrap(); - // happylock ensures at runtime there are no duplicate locks - let collection = LockCollection::try_new((&DATA_1, &DATA_2)).unwrap(); - let mut guard = collection.lock(key); + // happylock ensures at runtime there are no duplicate locks + let collection = LockCollection::try_new((&DATA_1, &DATA_2)).unwrap(); + let mut guard = collection.lock(key); - *guard.1 = (100 - *guard.0).to_string(); - *guard.0 += 1; - }); + *guard.1 = (100 - *guard.0).to_string(); + *guard.0 += 1; + }); } let key = ThreadKey::get().unwrap(); @@ -67,32 +76,65 @@ println!("{}", *data.0); println!("{}", *data.1); ``` +In many cases, the [`LockCollection::new`] or [`LockCollection::new_ref`] method can be used, improving performance. + +```rust +use std::thread; +use happylock::{LockCollection, Mutex, ThreadKey}; + +const N: usize = 100; + +static DATA: [Mutex; 2] = [Mutex::new(0), Mutex::new(1)]; + +for _ in 0..N { + thread::spawn(move || { + let key = ThreadKey::get().unwrap(); + // a reference to a type that implements `OwnedLockable` will never + // contain duplicates, so no duplicate checking is needed. + let collection = LockCollection::new_ref(&DATA); + let mut guard = collection.lock(key); + let x = *guard[1]; + *guard[1] += *guard[0]; + *guard[0] = x; + }); +} + +let key = ThreadKey::get().unwrap(); +let data = LockCollection::new_ref(&DATA); +let data = data.lock(key); + +println!("{}", data[0]); +println!("{}", data[1]); +``` + ## Performance **The `ThreadKey` is a mostly-zero cost abstraction.** It doesn't use any memory, and it doesn't really exist at run-time. The only cost comes from calling `ThreadKey::get()`, because the function has to ensure at runtime that the key hasn't already been taken. Dropping the key will also have a small cost. -**Avoid `LockCollection::try_new`.** This constructor will check to make sure that the collection contains no duplicate locks. This is an O(nlogn) operation, where n is the number of locks in the collection. `LockCollection::new` and `LockCollection::new_ref` don't need these checks because they use `OwnedLockable`, which is guaranteed to be unique as long as it is accessible. As a last resort, `LockCollection::new_unchecked` doesn't do this check, but is unsafe to call. +**Consider [`OwnedLockCollection`].** This will almost always be the fastest lock collection. It doesn't expose the underlying collection immutably, which means that it will always be locked in the same order, and doesn't need any sorting. + +**Avoid [`LockCollection::try_new`].** This constructor will check to make sure that the collection contains no duplicate locks. In most cases, this is O(nlogn), where n is the number of locks in the collections but in the case of [`RetryingLockCollection`], it's close to O(n). [`LockCollection::new`] and [`LockCollection::new_ref`] don't need these checks because they use [`OwnedLockable`], which is guaranteed to be unique as long as it is accessible. As a last resort [`LockCollection::new_unchecked`] doesn't do this check, but is unsafe to call. -**Avoid using distinct lock orders for `RetryingLockCollection`.** The problem is that this collection must iterate through the list of locks, and not complete until every single one of them is unlocked. This also means that attempting to lock multiple mutexes gives you a lower chance of ever running. Only one needs to be locked for the operation to need a reset. This problem can be prevented by not doing that in your code. Resources should be obtained in the same order on every thread. +**Know how to use [`RetryingLockCollection`].** This collection doesn't do any sorting, but uses a wasteful lock algorithm. It can't rely on the order of the locks to be the same across threads, so if it finds a lock that it can't acquire without blocking, it'll first release all of the locks it already acquired to avoid blocking other threads. This is wasteful because this algorithm may end up re-acquiring the same lock multiple times. To avoid this, ensure that (1) the first lock in the collection is always the first lock in any collection it appears in, and (2) the other locks in the collection are always preceded by that first lock. This will prevent any wasted time from re-acquiring locks. If you're unsure, [`LockCollection`] is a sensible default. ## Future Work It might to possible to break the `ThreadKey` system by having two crates import this crate and call `ThreadKey::get`. I'm not quite sure how this works, but Rust could decide to give each crate their own key, ergo one thread would get two keys. I don't think the standard library would have this issue. At a certain point, I have to recognize that someone could also just import the standard library mutex and get a deadlock that way. -We should add `Condvar` at some point. I didn't because I've never used it before, and I'm probably not the right person to solve this problem. I think all the synchronization problems could be solved by having `Condvar::wait` take a `ThreadKey` instead of a `MutexGuard`. Something similar can probably be done for `Barrier`. But again, I'm no expert. +Are the ergonomics here any good? This is completely uncharted territory. Maybe there are some useful helper methods we don't have here yet. Maybe `try_lock` should return a `Result`. Maybe `lock_api` or `spin` implements some useful methods that I kept out for this proof of concept. Maybe there are some lock-specific methods that could be added to `LockCollection`. More types might be lockable using a lock collection. -Do `OnceLock` or `LazyLock` ever deadlock? We might not need to add those here. - -It'd be nice to be able to use the mutexes built into the operating system, saving on binary size. Using `std::sync::Mutex` sounds promising, but it doesn't implement `RawMutex`, and implementing that is very difficult, if not impossible. +It'd be nice to be able to use the mutexes built into the operating system, saving on binary size. Using `std::sync::Mutex` sounds promising, but it doesn't implement `RawMutex`, and implementing that is very difficult, if not impossible. Maybe I could implement my own abstraction over the OS mutexes. I could also simply implement `Lockable` for the standard library mutex. Personally, I don't like mutex poisoning, but maybe it can be worked into the library if you're into that sort of thing. -Are the ergonomics here any good? This is completely uncharted territory. Maybe there are some useful helper methods we don't have here yet. Maybe `try_lock` should return a `Result`. Maybe `lock_api` or `spin` implements some useful methods that I kept out for this proof of concept. Maybe there are some lock-specific methods that could be added to `LockCollection`. More types might be lockable using a `LockGuard`. +It'd be interesting to add some methods such as `lock_clone` or `lock_swap`. This would still require a thread key, in case the mutex is already locked. The only way this could be done without a thread key is with a `&mut Mutex`, but we already have `as_mut`. A `try_lock_clone` or `try_lock_swap` might not need a `ThreadKey` though. A special lock that looks like `Cell` but implements `Sync` could be shared without a thread key, because the lock would be dropped immediately (preventing non-preemptive allocation). It might make some common operations easier. -I want to try to get this working without the standard library. There are a few problems with this though. For instance, this crate uses `thread_local` to allow other threads to have their own keys. Also, the only practical type of mutex that would work is a spinlock. Although, more could be implemented using the `RawMutex` trait. The `LockCollection` requires memory allocation at this time in order to check for duplicate locks. +Now that we have the `Sharable` trait, indicating that all of the locks in a collection can be shared, we could implement a `Readonly` wrapper around the collections that don't allow access to `lock` and `try_lock`. The idea would be that if you're not exclusively locking the collection, then you don't need to check for duplicates in the collection. Calling `.read()` on the same `RwLock` twice dooes not cause a deadlock. -It'd be interesting to add some methods such as `lock_clone` or `lock_swap`. This would still require a thread key, in case the mutex is already locked. The only way this could be done without a thread key is with a `&mut Mutex`, but we already have `get_mut`. A `try_lock_clone` or `try_lock_swap` might not need a `ThreadKey` though. A special lock that looks like `Cell` but implements `Sync` could be shared without a thread key, because the lock would be dropped immediately (preventing non-preemptive allocation). It might make some common operations easier. +I want to try to get this working without the standard library. There are a few problems with this though. For instance, this crate uses `thread_local` to allow other threads to have their own keys. Also, the only practical type of mutex that would work is a spinlock. Although, more could be implemented using the `RawMutex` trait. The `Lockable` trait requires memory allocation at this time in order to check for duplicate locks. -There might be some use in trying to prevent circular wait. There could be a special type that only allows the locking mutexes in a specific order. This would still require a thread key so that nobody tries to unlock multiple lock sequences at the same time. The biggest problem is that `LockSequence::lock_next` would need to return the same value each time, which is not very flexible. Most use cases for this are solved already by using `LockCollection`. +I've been thinking about addiung `Condvar` and `Barrier`, but I've been stopped by two things. I don't use either of those very often, so I'm probably not the right person to try to implement either of them. They're also weird, and harder to prevent deadlocking for. They're sort of the opposite of a mutex, since a mutex guarantees that at least one thread can always access each resource. + +Do `OnceLock` or `LazyLock` ever deadlock? We might not need to add those here. -Some sort of `DynamicLock` type might be useful so that, for example, a `Mutex` and an `RwLock` could be unlocked at the same time inside of a `Vec>`. Although, this wouldn't solve the problem of needing a `Mutex` and a `Mutex` at the same time. This would be better solved usin the existing tuple system. +We could implement special methods for something like a `LockCollection>` where we only lock the first three items. diff --git a/examples/fibonacci.rs b/examples/fibonacci.rs new file mode 100644 index 0000000..d43b01c --- /dev/null +++ b/examples/fibonacci.rs @@ -0,0 +1,29 @@ +use happylock::{LockCollection, Mutex, ThreadKey}; +use std::thread; + +const N: usize = 36; + +static DATA: [Mutex; 2] = [Mutex::new(0), Mutex::new(1)]; + +fn main() { + for _ in 0..N { + thread::spawn(move || { + let key = ThreadKey::get().unwrap(); + + // a reference to a type that implements `OwnedLockable` will never + // contain duplicates, so no duplicate checking is needed. + let collection = LockCollection::new_ref(&DATA); + let mut guard = collection.lock(key); + + let x = *guard[1]; + *guard[1] += *guard[0]; + *guard[0] = x; + }); + } + + let key = ThreadKey::get().unwrap(); + let data = LockCollection::new_ref(&DATA); + let data = data.lock(key); + println!("{}", data[0]); + println!("{}", data[1]); +} diff --git a/src/lib.rs b/src/lib.rs index 673d279..9c39c6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ //! # Theory //! //! There are four conditions necessary for a deadlock to occur. In order to -//! prevent deadlocks, we need to prevent one of the following: +//! prevent deadlocks, we just need to prevent one of the following: //! //! 1. mutual exclusion //! 2. non-preemptive allocation @@ -27,24 +27,10 @@ //! first. Requesting multiple resources at once is atomic. You either get all //! the requested resources or none at all. //! -//! # Performance -//! -//! **Avoid [`LockCollection::try_new`].** This constructor will check to make -//! sure that the collection contains no duplicate locks. This is an O(n^2) -//! operation, where n is the number of locks in the collection. -//! [`LockCollection::new`] and [`LockCollection::new_ref`] don't need these -//! checks because they use [`OwnedLockable`], which is guaranteed to be unique -//! as long as it is accessible. As a last resort, -//! [`LockCollection::new_unchecked`] doesn't do this check, but is unsafe to -//! call. -//! -//! **Avoid using distinct lock orders for [`LockCollection`].** The problem is -//! that this library must iterate through the list of locks, and not complete -//! until every single one of them is unlocked. This also means that attempting -//! to lock multiple mutexes gives you a lower chance of ever running. Only one -//! needs to be locked for the operation to need a reset. This problem can be -//! prevented by not doing that in your code. Resources should be obtained in -//! the same order on every thread. +//! As an optimization, this library also often prevents **circular wait**. +//! Many collections sort the locks in order of their memory address. As long +//! as the locks are always acquired in that order, then time doesn't need to +//! be wasted on releasing locks after a failure and re-acquiring them later. //! //! # Examples //! @@ -105,6 +91,78 @@ //! println!("{}", *data.0); //! println!("{}", *data.1); //! ``` +//! +//! In many cases, the [`LockCollection::new`] or [`LockCollection::new_ref`] +//! method can be used, improving performance. +//! +//! ```rust +//! use std::thread; +//! use happylock::{LockCollection, Mutex, ThreadKey}; +//! +//! const N: usize = 100; +//! +//! static DATA: [Mutex; 2] = [Mutex::new(0), Mutex::new(1)]; +//! +//! for _ in 0..N { +//! thread::spawn(move || { +//! let key = ThreadKey::get().unwrap(); +//! +//! // a reference to a type that implements `OwnedLockable` will never +//! // contain duplicates, so no duplicate checking is needed. +//! let collection = LockCollection::new_ref(&DATA); +//! let mut guard = collection.lock(key); +//! +//! let x = *guard[1]; +//! *guard[1] += *guard[0]; +//! *guard[0] = x; +//! }); +//! } +//! +//! let key = ThreadKey::get().unwrap(); +//! let data = LockCollection::new_ref(&DATA); +//! let data = data.lock(key); +//! println!("{}", data[0]); +//! println!("{}", data[1]); +//! ``` +//! +//! # Performance +//! +//! **The `ThreadKey` is a mostly-zero cost abstraction.** It doesn't use any +//! memory, and it doesn't really exist at run-time. The only cost comes from +//! calling `ThreadKey::get()`, because the function has to ensure at runtime +//! that the key hasn't already been taken. Dropping the key will also have a +//! small cost. +//! +//! **Consider [`OwnedLockCollection`].** This will almost always be the +//! fastest lock collection. It doesn't expose the underlying collection +//! immutably, which means that it will always be locked in the same order, and +//! doesn't need any sorting. +//! +//! **Avoid [`LockCollection::try_new`].** This constructor will check to make +//! sure that the collection contains no duplicate locks. In most cases, this +//! is O(nlogn), where n is the number of locks in the collections but in the +//! case of [`RetryingLockCollection`], it's close to O(n). +//! [`LockCollection::new`] and [`LockCollection::new_ref`] don't need these +//! checks because they use [`OwnedLockable`], which is guaranteed to be unique +//! as long as it is accessible. As a last resort, +//! [`LockCollection::new_unchecked`] doesn't do this check, but is unsafe to +//! call. +//! +//! **Know how to use [`RetryingLockCollection`].** This collection doesn't do +//! any sorting, but uses a wasteful lock algorithm. It can't rely on the order +//! of the locks to be the same across threads, so if it finds a lock that it +//! can't acquire without blocking, it'll first release all of the locks it +//! already acquired to avoid blocking other threads. This is wasteful because +//! this algorithm may end up re-acquiring the same lock multiple times. To +//! avoid this, ensure that (1) the first lock in the collection is always the +//! first lock in any collection it appears in, and (2) the other locks in the +//! collection are always preceded by that first lock. This will prevent any +//! wasted time from re-acquiring locks. If you're unsure, [`LockCollection`] +//! is a sensible default. +//! +//! [`OwnedLockable`]: `lockable::OwnedLockable` +//! [`OwnedLockCollection`]: `collection::OwnedLockCollection` +//! [`RetryingLockCollection`]: `collection::RetryingLockCollection` mod key; -- cgit v1.2.3 From 878f4fae4d3c6e64ab3824bf3fc012fbb5293a21 Mon Sep 17 00:00:00 2001 From: Botahamec Date: Wed, 22 May 2024 20:59:09 -0400 Subject: Documentation --- README.md | 8 +- happylock.md | 339 ++++++++++++++++++++++++++++++++++++++++++++++ src/collection.rs | 2 + src/collection/boxed.rs | 231 +++++++++++++++++++++++++++++++ src/collection/owned.rs | 156 +++++++++++++++++++++ src/collection/ref.rs | 116 ++++++++++++++-- src/collection/retry.rs | 248 ++++++++++++++++++++++++++++++++- src/lockable.rs | 336 ++++----------------------------------------- src/mutex/guard.rs | 4 +- src/rwlock/read_guard.rs | 4 +- src/rwlock/read_lock.rs | 2 +- src/rwlock/write_guard.rs | 4 +- 12 files changed, 1122 insertions(+), 328 deletions(-) create mode 100644 happylock.md (limited to 'README.md') diff --git a/README.md b/README.md index 01c259e..04e5a71 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,10 @@ let data = data.lock(&mut key); println!("{}", *data); ``` -Unlocking a mutex requires a `ThreadKey` or a mutable reference to `ThreadKey`. Each thread will be allowed to have one key at a time, but no more than that. The `ThreadKey` type is not cloneable or copyable. This means that only one thing can be locked at a time. +Unlocking a mutex requires a `ThreadKey` or a mutable reference to `ThreadKey`. +Each thread will be allowed to have one key at a time, but no more than that. +The `ThreadKey` type is not cloneable or copyable. This means that only one +thing can be locked at a time. To lock multiple mutexes at a time, create a `LockCollection`. @@ -76,7 +79,8 @@ println!("{}", *data.0); println!("{}", *data.1); ``` -In many cases, the [`LockCollection::new`] or [`LockCollection::new_ref`] method can be used, improving performance. +In many cases, the [`LockCollection::new`] or [`LockCollection::new_ref`] +method can be used, improving performance. ```rust use std::thread; diff --git a/happylock.md b/happylock.md new file mode 100644 index 0000000..477e99b --- /dev/null +++ b/happylock.md @@ -0,0 +1,339 @@ +--- +marp: true +theme: gaia +class: invert +--- + + + +# HappyLock + +deadlock-free mutexes at compile-time + +--- + +## Four Conditions for Deadlock + +1. Mutual Exclusion +2. Non-preemptive Allocation +3. Cyclic Wait +4. Partial Allocation + +--- + +## Preventing Mutual Exclusion + +Mutual exclusion is the entire point of a mutex. + +Do you want a `ReadOnly` type? + +Just use `Arc` or `&T`! + +--- + +## Prevent Non-Preemptive Allocation + +```rust +let mutex = Mutex::new(10); +let mut number = mutex.lock(); + +let th = thread::spawn(|| { + let number = mutex.lock(); // preempts the other lock on number +}); +th.join(); + +prinln!("Thread 1: {}", *number); // oops, we don't have access to number anymore! +``` + +--- + +## Preventing Cyclic Wait + +The language needs to enforce that all locks are acquired in the same order. + +Rust doesn't have a built-in mechanism which can provide this. + +Even if it could keep the locks in a certain order, using a `OrderedLock` type, we wouldn't be able to force you to use the mechanism. + +And you could create two `OrderedLock` types and get deadlock using that. + +--- + +## Preventing Partial Allocation + +The language needs to enforce *total allocation*. + +Acquiring a new lock requires releasing all currently-held locks. + +**This will be our approach for now.** + +--- + +## Quick Refresh on Borrow Checker Rules + +1. You may have multiple immutable references to a value at a time +2. If there is a mutable reference to a value, then it is the only reference +3. Values cannot be moved while they are being referenced + +```rust +let s = String::new("Hello, world!"); +let r1 = &s; +let r2 = &s; // this is allowed because of #1 +let mr = &mut s; // illegal: rule #2 +drop(s); // also illegal: rule #3 +println!("{r1} {r2}"); +``` + +--- + +## How could an Operating System do this? + +```c +#include + +void main() { + os_mutex_t m = os_mutex_create(); + + // the os returns an error if the rules aren't followed + if (os_mutex_lock(&m)) { + printf("Error!\n"); + } + + return 0; +} +``` + +--- + +## We have technology! (the borrow checker) + +```rust +use happylock::{ThreadKey, Mutex}; + +fn main() { + // each thread can only have one thread key (that's why we unwrap) + // ThreadKey is not Send, Sync, Copy, or Clone + let key = ThreadKey::get().unwrap(); + + let mutex = Mutex::new(10); + + // locking a mutex requires either the ThreadKey or a &mut ThreadKey + let mut guard = mutex.lock(key); + // this means that a thread cannot lock more than one thing at a time + + println!("{}", *guard); +} +``` + +--- + +## Performance: it's freaking fast + +`ThreadKey` is a mostly zero-cosst abstraction. It takes no memory at runtime. The only cost is getting and dropping the key. + +`Mutex` is a thin wrapper around `parking_lot`. There's also a `spin` backend if needed for some reason. + +--- + +## Wait, I need two mutexes + +```rust +use happylock::{ThreadKey, Mutex, LockCollection}; + +fn main() { + let key = ThreadKey::get().unwrap(); + let mutex1 = Mutex::new(5); + let mutex2 = Mutex::new(String::new()); + + let collection = LockCollection::new((mutex1, mutex2)); + let guard = collection.lock(key); + + *guard.1 = format!("{}{}", *guard.1, guard.0); + *guard.0 += 1; +} +``` + +--- + +## The Lockable API + +```rust +unsafe trait Lockable { + type Guard; + + unsafe fn lock(&self) -> Self::Guard; + + unsafe fn try_lock(&self) -> Option; +} +``` + +--- + +## That's cool! Lemme try something + +```rust +use happylock::{ThreadKey, Mutex, LockCollection}; + +fn main() { + let key = ThreadKey::get().unwrap(); + let mutex1 = Mutex::new(5); + + // oh no. this will deadlock us + let collection = LockCollection::new((&mutex1, &mutex1)); + let guard = collection.lock(key); + + // the good news is: this doesn't compile +} +``` + +--- + +## LockCollection's stub + +```rust +impl LockCollection { + pub fn new(data: L) -> Self { /***/ } +} + +impl LockCollection<&L> { + pub fn new_ref(data: &L) -> Self { /***/ } +} + +impl LockCollection { + // checks for duplicates + pub fn try_new(data: L) -> Option { /***/ } + + pub unsafe fn new_unchecked(data: L) -> Self { /***/ } +} +``` + +--- + +## Changes to Lockable + +```rust +unsafe trait Lockable { + // ... + + fn get_ptrs(&self) -> Vec; +} + + + +// not implemented for &L +// ergo: the values within are guaranteed to be unique +unsafe trait OwnedLockable: Lockable {} + + +``` + +--- + +## `contains_duplicates` (1st attempt) + +```rust +fn contains_duplicates(data: L) -> bool { + let pointers = data.get_ptrs(); + for (i, ptr1) in pointers.iter().enumerate() { + for ptr2 in pointers.iter().take(i) { + if ptr1 == ptr2 { + return true; + } + } + } + + false +} +``` + +Time Complexity: O(n²) + +--- + +## 2nd attempt: sorting the pointers + +```rust +fn contains_duplicates(data: L) -> bool { + let mut pointers = data.get_ptrs(); + pointers.sort_unstable(); + pointers.windows(2).any(|w| w[0] == w[1]) +} +``` + +Time Complexity: O(nlogn) + +--- + +## Missing Features + +- `Condvar`/`Barrier` +- We probably don't need `OnceLock` or `LazyLock` +- Standard Library Backend +- Mutex poisoning +- Support for `no_std` +- Convenience methods: `lock_swap`, `lock_set`? +- `try_lock_swap` doesn't need a `ThreadKey` +- Going further: `LockCell` API (preemptive allocation) + +--- + + + +## What's next? + +--- + +## Problem: Live-locking + +Although this library is able to successfully prevent deadlocks, livelocks may still be an issue. Imagine thread 1 gets resource 1, thread 2 gets resource 2, thread 1 realizes it can't get resource 2, thread 2 realizes it can't get resource 1, thread 1 drops resource 1, thread 2 drops resource 2, and then repeat forever. In practice, this situation probably wouldn't last forever. But it would be nice if this could be prevented somehow. + +--- + +## Solution: Switch to preventing cyclic wait + +- We're already sorting the pointers by memory address. +- So let's keep that order! + +--- + +## Problems with This Approach + +- I can't sort a tuple + - Don't return them sorted, silly +- Indexing the locks in the right order + - Have `get_ptrs` return a `&dyn Lock` + - Start by locking everything + - Then call a separate `guard` method to create the guard + +--- + +# New traits + +```rust +unsafe trait Lock { + unsafe fn lock(&self); + unsafe fn try_lock(&self) -> bool; + unsafe fn unlock(&self); +} + +unsafe trait Lockable { // this is a bad name (LockGroup?) + type Guard<'g>; + fn get_locks<'a>(&'a self, &mut Vec<&'a dyn Lock>); + unsafe fn guard<'g>(&'g self) -> Self::Guard<'g>; +} +``` + +--- + +## Ok let's get started, oh wait + +- self-referential data structures + - for best performance: `RefLockCollection`, `BoxedLockCollection`, `OwnedLockCollection` +- `LockCollection::new_ref` doesn't work without sorting + - So as a fallback, provide `RetryingLockCollection`. It doesn't do any sorting, but unlikely to ever acquire the lock + +--- + + + +## The End diff --git a/src/collection.rs b/src/collection.rs index c51e3cf..5dc6946 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -103,6 +103,8 @@ pub struct RetryingLockCollection { } /// A RAII guard for a generic [`Lockable`] type. +/// +/// [`Lockable`]: `crate::lockable::Lockable` pub struct LockGuard<'key, Guard, Key: Keyable + 'key> { guard: Guard, key: Key, diff --git a/src/collection/boxed.rs b/src/collection/boxed.rs index ea840ab..224eedb 100644 --- a/src/collection/boxed.rs +++ b/src/collection/boxed.rs @@ -119,6 +119,19 @@ impl From for BoxedLockCollection { } impl BoxedLockCollection { + /// Creates a new collection of owned locks. + /// + /// Because the locks are owned, there's no need to do any checks for + /// duplicate values. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, LockCollection}; + /// + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = LockCollection::new(data); + /// ``` #[must_use] pub fn new(data: L) -> Self { // safety: owned lockable types cannot contain duplicates @@ -127,6 +140,19 @@ impl BoxedLockCollection { } impl<'a, L: OwnedLockable> BoxedLockCollection<&'a L> { + /// Creates a new collection of owned locks. + /// + /// Because the locks are owned, there's no need to do any checks for + /// duplicate values. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, LockCollection}; + /// + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = LockCollection::new_ref(&data); + /// ``` #[must_use] pub fn new_ref(data: &'a L) -> Self { // safety: owned lockable types cannot contain duplicates @@ -135,11 +161,31 @@ impl<'a, L: OwnedLockable> BoxedLockCollection<&'a L> { } impl BoxedLockCollection { + /// Creates a new collections of locks. + /// + /// # Safety + /// + /// This results in undefined behavior if any locks are presented twice + /// within this collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, LockCollection}; + /// + /// let data1 = Mutex::new(0); + /// let data2 = Mutex::new(""); + /// + /// // safety: data1 and data2 refer to distinct mutexes + /// let data = (&data1, &data2); + /// let lock = unsafe { LockCollection::new_unchecked(&data) }; + /// ``` #[must_use] pub unsafe fn new_unchecked(data: L) -> Self { let data = Box::new(data); let mut locks = Vec::new(); data.get_ptrs(&mut locks); + locks.sort_by_key(|lock| std::ptr::from_ref(*lock).cast::<()>() as usize); // safety: the box will be dropped after the lock references, so it's // safe to just pretend they're static @@ -147,6 +193,23 @@ impl BoxedLockCollection { Self { data, locks } } + /// Creates a new collection of locks. + /// + /// This returns `None` if any locks are found twice in the given + /// collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, LockCollection}; + /// + /// let data1 = Mutex::new(0); + /// let data2 = Mutex::new(""); + /// + /// // data1 and data2 refer to distinct mutexes, so this won't panic + /// let data = (&data1, &data2); + /// let lock = LockCollection::try_new(&data).unwrap(); + /// ``` #[must_use] pub fn try_new(data: L) -> Option { // safety: we are checking for duplicates before returning @@ -159,11 +222,48 @@ impl BoxedLockCollection { } } + /// Gets the underlying collection, consuming this collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey, LockCollection}; + /// + /// let data1 = Mutex::new(42); + /// let data2 = Mutex::new(""); + /// + /// // data1 and data2 refer to distinct mutexes, so this won't panic + /// let data = (&data1, &data2); + /// let lock = LockCollection::try_new(&data).unwrap(); + /// + /// let key = ThreadKey::get().unwrap(); + /// let guard = lock.into_inner().0.lock(key); + /// assert_eq!(*guard, 42); + /// ``` #[must_use] pub fn into_inner(self) -> Box { self.data } + /// Locks the collection + /// + /// This function returns a guard that can be used to access the underlying + /// data. When the guard is dropped, the locks in the collection are also + /// dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = LockCollection::new(data); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// ``` pub fn lock<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -181,6 +281,30 @@ impl BoxedLockCollection { } } + /// Attempts to lock the without blocking. + /// + /// If successful, this method returns a guard that can be used to access + /// the data, and unlocks the data when it is dropped. Otherwise, `None` is + /// returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = LockCollection::new(data); + /// + /// match lock.try_lock(key) { + /// Some(mut guard) => { + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_lock<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -210,6 +334,23 @@ impl BoxedLockCollection { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = LockCollection::new(data); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// let key = LockCollection::<(Mutex, Mutex<&str>)>::unlock(guard); + /// ``` pub fn unlock<'key, Key: Keyable + 'key>(guard: LockGuard<'key, L::Guard<'_>, Key>) -> Key { drop(guard.guard); guard.key @@ -217,6 +358,25 @@ impl BoxedLockCollection { } impl BoxedLockCollection { + /// Locks the collection, so that other threads can still read from it + /// + /// This function returns a guard that can be used to access the underlying + /// data immutably. When the guard is dropped, the locks in the collection + /// are also dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = LockCollection::new(data); + /// + /// let mut guard = lock.read(key); + /// assert_eq!(*guard.0, 0); + /// assert_eq!(*guard.1, ""); + /// ``` pub fn read<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -234,6 +394,31 @@ impl BoxedLockCollection { } } + /// Attempts to lock the without blocking, in such a way that other threads + /// can still read from the collection. + /// + /// If successful, this method returns a guard that can be used to access + /// the data immutably, and unlocks the data when it is dropped. Otherwise, + /// `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(5), RwLock::new("6")); + /// let lock = LockCollection::new(data); + /// + /// match lock.try_read(key) { + /// Some(mut guard) => { + /// assert_eq!(*guard.0, 5); + /// assert_eq!(*guard.1, "6"); + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_read<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -263,6 +448,21 @@ impl BoxedLockCollection { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = LockCollection::new(data); + /// + /// let mut guard = lock.read(key); + /// let key = LockCollection::<(RwLock, RwLock<&str>)>::unlock_read(guard); + /// ``` pub fn unlock_read<'key, Key: Keyable + 'key>( guard: LockGuard<'key, L::ReadGuard<'_>, Key>, ) -> Key { @@ -276,6 +476,22 @@ where &'a L: IntoIterator, { /// Returns an iterator over references to each value in the collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(26), Mutex::new(1)]; + /// let lock = LockCollection::new(data); + /// + /// let mut iter = lock.iter(); + /// let mutex = iter.next().unwrap(); + /// let guard = mutex.lock(key); + /// + /// assert_eq!(*guard, 26); + /// ``` #[must_use] pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter { self.into_iter() @@ -288,6 +504,21 @@ where { /// Returns an iterator over mutable references to each value in the /// collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey, LockCollection}; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(26), Mutex::new(1)]; + /// let mut lock = LockCollection::new(data); + /// + /// let mut iter = lock.iter_mut(); + /// let mutex = iter.next().unwrap(); + /// + /// assert_eq!(*mutex.as_mut(), 26); + /// ``` #[must_use] pub fn iter_mut(&'a mut self) -> <&'a mut L as IntoIterator>::IntoIter { self.into_iter() diff --git a/src/collection/owned.rs b/src/collection/owned.rs index d77d568..e1549b2 100644 --- a/src/collection/owned.rs +++ b/src/collection/owned.rs @@ -79,16 +79,67 @@ impl From for OwnedLockCollection { } impl OwnedLockCollection { + /// Creates a new collection of owned locks. + /// + /// Because the locks are owned, there's no need to do any checks for + /// duplicate values. The locks also don't need to be sorted by memory + /// address because they aren't used anywhere else. + /// + /// # Examples + /// + /// ``` + /// use happylock::Mutex; + /// use happylock::collection::OwnedLockCollection; + /// + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = OwnedLockCollection::new(data); + /// ``` #[must_use] pub const fn new(data: L) -> Self { Self { data } } + /// Gets the underlying collection, consuming this collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let data = (Mutex::new(42), Mutex::new("")); + /// let lock = OwnedLockCollection::new(data); + /// + /// let key = ThreadKey::get().unwrap(); + /// let inner = lock.into_inner(); + /// let guard = inner.0.lock(key); + /// assert_eq!(*guard, 42); + /// ``` #[must_use] pub fn into_inner(self) -> L { self.data } + /// Locks the collection + /// + /// This function returns a guard that can be used to access the underlying + /// data. When the guard is dropped, the locks in the collection are also + /// dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = OwnedLockCollection::new(data); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// ``` pub fn lock<'g, 'key, Key: Keyable + 'key>( &'g self, key: Key, @@ -109,6 +160,31 @@ impl OwnedLockCollection { } } + /// Attempts to lock the without blocking. + /// + /// If successful, this method returns a guard that can be used to access + /// the data, and unlocks the data when it is dropped. Otherwise, `None` is + /// returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = OwnedLockCollection::new(data); + /// + /// match lock.try_lock(key) { + /// Some(mut guard) => { + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_lock<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -139,6 +215,24 @@ impl OwnedLockCollection { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = OwnedLockCollection::new(data); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// let key = OwnedLockCollection::<(Mutex, Mutex<&str>)>::unlock(guard); + /// ``` #[allow(clippy::missing_const_for_fn)] pub fn unlock<'g, 'key: 'g, Key: Keyable + 'key>( guard: LockGuard<'key, L::Guard<'g>, Key>, @@ -149,6 +243,26 @@ impl OwnedLockCollection { } impl OwnedLockCollection { + /// Locks the collection, so that other threads can still read from it + /// + /// This function returns a guard that can be used to access the underlying + /// data immutably. When the guard is dropped, the locks in the collection + /// are also dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = OwnedLockCollection::new(data); + /// + /// let mut guard = lock.read(key); + /// assert_eq!(*guard.0, 0); + /// assert_eq!(*guard.1, ""); + /// ``` pub fn read<'g, 'key, Key: Keyable + 'key>( &'g self, key: Key, @@ -169,6 +283,32 @@ impl OwnedLockCollection { } } + /// Attempts to lock the without blocking, in such a way that other threads + /// can still read from the collection. + /// + /// If successful, this method returns a guard that can be used to access + /// the data immutably, and unlocks the data when it is dropped. Otherwise, + /// `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(5), RwLock::new("6")); + /// let lock = OwnedLockCollection::new(data); + /// + /// match lock.try_read(key) { + /// Some(mut guard) => { + /// assert_eq!(*guard.0, 5); + /// assert_eq!(*guard.1, "6"); + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_read<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -199,6 +339,22 @@ impl OwnedLockCollection { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::OwnedLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = OwnedLockCollection::new(data); + /// + /// let mut guard = lock.read(key); + /// let key = OwnedLockCollection::<(RwLock, RwLock<&str>)>::unlock_read(guard); + /// ``` #[allow(clippy::missing_const_for_fn)] pub fn unlock_read<'g, 'key: 'g, Key: Keyable + 'key>( guard: LockGuard<'key, L::ReadGuard<'g>, Key>, diff --git a/src/collection/ref.rs b/src/collection/ref.rs index 2e2883a..e5c548f 100644 --- a/src/collection/ref.rs +++ b/src/collection/ref.rs @@ -82,10 +82,11 @@ impl<'a, L: OwnedLockable> RefLockCollection<'a, L> { /// # Examples /// /// ``` - /// use happylock::{LockCollection, Mutex}; + /// use happylock::Mutex; + /// use happylock::collection::RefLockCollection; /// /// let data = (Mutex::new(0), Mutex::new("")); - /// let lock = LockCollection::new(&data); + /// let lock = RefLockCollection::new(&data); /// ``` #[must_use] pub fn new(data: &'a L) -> RefLockCollection { @@ -107,13 +108,15 @@ impl<'a, L: Lockable> RefLockCollection<'a, L> { /// # Examples /// /// ``` - /// use happylock::{LockCollection, Mutex}; + /// use happylock::Mutex; + /// use happylock::collection::RefLockCollection; /// /// let data1 = Mutex::new(0); /// let data2 = Mutex::new(""); /// /// // safety: data1 and data2 refer to distinct mutexes - /// let lock = unsafe { LockCollection::new_unchecked((&data1, &data2)) }; + /// let data = (&data1, &data2); + /// let lock = unsafe { RefLockCollection::new_unchecked(&data) }; /// ``` #[must_use] pub unsafe fn new_unchecked(data: &'a L) -> Self { @@ -131,13 +134,15 @@ impl<'a, L: Lockable> RefLockCollection<'a, L> { /// # Examples /// /// ``` - /// use happylock::{LockCollection, Mutex}; + /// use happylock::Mutex; + /// use happylock::collection::RefLockCollection; /// /// let data1 = Mutex::new(0); /// let data2 = Mutex::new(""); /// /// // data1 and data2 refer to distinct mutexes, so this won't panic - /// let lock = LockCollection::try_new((&data1, &data2)).unwrap(); + /// let data = (&data1, &data2); + /// let lock = RefLockCollection::try_new(&data).unwrap(); /// ``` #[must_use] pub fn try_new(data: &'a L) -> Option { @@ -158,10 +163,12 @@ impl<'a, L: Lockable> RefLockCollection<'a, L> { /// # Examples /// /// ``` - /// use happylock::{LockCollection, Mutex, ThreadKey}; + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RefLockCollection; /// /// let key = ThreadKey::get().unwrap(); - /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RefLockCollection::new(&data); /// /// let mut guard = lock.lock(key); /// *guard.0 += 1; @@ -193,10 +200,12 @@ impl<'a, L: Lockable> RefLockCollection<'a, L> { /// # Examples /// /// ``` - /// use happylock::{LockCollection, Mutex, ThreadKey}; + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RefLockCollection; /// /// let key = ThreadKey::get().unwrap(); - /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RefLockCollection::new(&data); /// /// match lock.try_lock(key) { /// Some(mut guard) => { @@ -242,15 +251,17 @@ impl<'a, L: Lockable> RefLockCollection<'a, L> { /// # Examples /// /// ``` - /// use happylock::{LockCollection, Mutex, ThreadKey}; + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RefLockCollection; /// /// let key = ThreadKey::get().unwrap(); - /// let lock = LockCollection::new((Mutex::new(0), Mutex::new(""))); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RefLockCollection::new(&data); /// /// let mut guard = lock.lock(key); /// *guard.0 += 1; /// *guard.1 = "1"; - /// let key = LockCollection::unlock(guard); + /// let key = RefLockCollection::<(Mutex, Mutex<&str>)>::unlock(guard); /// ``` #[allow(clippy::missing_const_for_fn)] pub fn unlock<'key: 'a, Key: Keyable + 'key>(guard: LockGuard<'key, L::Guard<'a>, Key>) -> Key { @@ -260,6 +271,26 @@ impl<'a, L: Lockable> RefLockCollection<'a, L> { } impl<'a, L: Sharable> RefLockCollection<'a, L> { + /// Locks the collection, so that other threads can still read from it + /// + /// This function returns a guard that can be used to access the underlying + /// data immutably. When the guard is dropped, the locks in the collection + /// are also dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::RefLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = RefLockCollection::new(&data); + /// + /// let mut guard = lock.read(key); + /// assert_eq!(*guard.0, 0); + /// assert_eq!(*guard.1, ""); + /// ``` pub fn read<'key: 'a, Key: Keyable + 'key>( &'a self, key: Key, @@ -277,6 +308,32 @@ impl<'a, L: Sharable> RefLockCollection<'a, L> { } } + /// Attempts to lock the without blocking, in such a way that other threads + /// can still read from the collection. + /// + /// If successful, this method returns a guard that can be used to access + /// the data immutably, and unlocks the data when it is dropped. Otherwise, + /// `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::RefLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(5), RwLock::new("6")); + /// let lock = RefLockCollection::new(&data); + /// + /// match lock.try_read(key) { + /// Some(mut guard) => { + /// assert_eq!(*guard.0, 5); + /// assert_eq!(*guard.1, "6"); + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_read<'key: 'a, Key: Keyable + 'key>( &'a self, key: Key, @@ -306,6 +363,22 @@ impl<'a, L: Sharable> RefLockCollection<'a, L> { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::RefLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = RefLockCollection::new(&data); + /// + /// let mut guard = lock.read(key); + /// let key = RefLockCollection::<(RwLock, RwLock<&str>)>::unlock_read(guard); + /// ``` #[allow(clippy::missing_const_for_fn)] pub fn unlock_read<'key: 'a, Key: Keyable + 'key>( guard: LockGuard<'key, L::ReadGuard<'a>, Key>, @@ -320,6 +393,23 @@ where &'a L: IntoIterator, { /// Returns an iterator over references to each value in the collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RefLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(26), Mutex::new(1)]; + /// let lock = RefLockCollection::new(&data); + /// + /// let mut iter = lock.iter(); + /// let mutex = iter.next().unwrap(); + /// let guard = mutex.lock(key); + /// + /// assert_eq!(*guard, 26); + /// ``` #[must_use] pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter { self.into_iter() diff --git a/src/collection/retry.rs b/src/collection/retry.rs index d15d7d6..2b9b0a0 100644 --- a/src/collection/retry.rs +++ b/src/collection/retry.rs @@ -6,12 +6,13 @@ use std::marker::PhantomData; use super::{LockGuard, RetryingLockCollection}; +/// Checks that a collection contains no duplicate references to a lock. fn contains_duplicates(data: L) -> bool { let mut locks = Vec::new(); data.get_ptrs(&mut locks); let locks = locks.into_iter().map(|l| l as *const dyn RawLock); - let mut locks_set = HashSet::new(); + let mut locks_set = HashSet::with_capacity(locks.len()); for lock in locks { if !locks_set.insert(lock) { return true; @@ -119,6 +120,21 @@ impl From for RetryingLockCollection { } impl RetryingLockCollection { + /// Creates a new collection of owned locks. + /// + /// Because the locks are owned, there's no need to do any checks for + /// duplicate values. The locks also don't need to be sorted by memory + /// address because they aren't used anywhere else. + /// + /// # Examples + /// + /// ``` + /// use happylock::Mutex; + /// use happylock::collection::RetryingLockCollection; + /// + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RetryingLockCollection::new(data); + /// ``` #[must_use] pub const fn new(data: L) -> Self { Self { data } @@ -126,6 +142,20 @@ impl RetryingLockCollection { } impl<'a, L: OwnedLockable> RetryingLockCollection<&'a L> { + /// Creates a new collection of owned locks. + /// + /// Because the locks are owned, there's no need to do any checks for + /// duplicate values. + /// + /// # Examples + /// + /// ``` + /// use happylock::Mutex; + /// use happylock::collection::RetryingLockCollection; + /// + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RetryingLockCollection::new_ref(&data); + /// ``` #[must_use] pub const fn new_ref(data: &'a L) -> Self { Self { data } @@ -133,19 +163,95 @@ impl<'a, L: OwnedLockable> RetryingLockCollection<&'a L> { } impl RetryingLockCollection { + /// Creates a new collections of locks. + /// + /// # Safety + /// + /// This results in undefined behavior if any locks are presented twice + /// within this collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::Mutex; + /// use happylock::collection::RetryingLockCollection; + /// + /// let data1 = Mutex::new(0); + /// let data2 = Mutex::new(""); + /// + /// // safety: data1 and data2 refer to distinct mutexes + /// let data = (&data1, &data2); + /// let lock = unsafe { RetryingLockCollection::new_unchecked(&data) }; + /// ``` #[must_use] pub const unsafe fn new_unchecked(data: L) -> Self { Self { data } } + /// Creates a new collection of locks. + /// + /// This returns `None` if any locks are found twice in the given + /// collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::Mutex; + /// use happylock::collection::RetryingLockCollection; + /// + /// let data1 = Mutex::new(0); + /// let data2 = Mutex::new(""); + /// + /// // data1 and data2 refer to distinct mutexes, so this won't panic + /// let data = (&data1, &data2); + /// let lock = RetryingLockCollection::try_new(&data).unwrap(); + /// ``` + #[must_use] pub fn try_new(data: L) -> Option { - contains_duplicates(&data).then_some(Self { data }) + (!contains_duplicates(&data)).then_some(Self { data }) } + /// Gets the underlying collection, consuming this collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let data = (Mutex::new(42), Mutex::new("")); + /// let lock = RetryingLockCollection::new(data); + /// + /// let key = ThreadKey::get().unwrap(); + /// let inner = lock.into_inner(); + /// let guard = inner.0.lock(key); + /// assert_eq!(*guard, 42); + /// ``` + #[must_use] pub fn into_inner(self) -> L { self.data } + /// Locks the collection + /// + /// This function returns a guard that can be used to access the underlying + /// data. When the guard is dropped, the locks in the collection are also + /// dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RetryingLockCollection::new(data); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// ``` pub fn lock<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -202,6 +308,31 @@ impl RetryingLockCollection { } } + /// Attempts to lock the without blocking. + /// + /// If successful, this method returns a guard that can be used to access + /// the data, and unlocks the data when it is dropped. Otherwise, `None` is + /// returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RetryingLockCollection::new(data); + /// + /// match lock.try_lock(key) { + /// Some(mut guard) => { + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_lock<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -241,6 +372,24 @@ impl RetryingLockCollection { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (Mutex::new(0), Mutex::new("")); + /// let lock = RetryingLockCollection::new(data); + /// + /// let mut guard = lock.lock(key); + /// *guard.0 += 1; + /// *guard.1 = "1"; + /// let key = RetryingLockCollection::<(Mutex, Mutex<&str>)>::unlock(guard); + /// ``` pub fn unlock<'key, Key: Keyable + 'key>(guard: LockGuard<'key, L::Guard<'_>, Key>) -> Key { drop(guard.guard); guard.key @@ -248,6 +397,26 @@ impl RetryingLockCollection { } impl RetryingLockCollection { + /// Locks the collection, so that other threads can still read from it + /// + /// This function returns a guard that can be used to access the underlying + /// data immutably. When the guard is dropped, the locks in the collection + /// are also dropped. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = RetryingLockCollection::new(data); + /// + /// let mut guard = lock.read(key); + /// assert_eq!(*guard.0, 0); + /// assert_eq!(*guard.1, ""); + /// ``` pub fn read<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -304,6 +473,32 @@ impl RetryingLockCollection { } } + /// Attempts to lock the without blocking, in such a way that other threads + /// can still read from the collection. + /// + /// If successful, this method returns a guard that can be used to access + /// the data immutably, and unlocks the data when it is dropped. Otherwise, + /// `None` is returned. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(5), RwLock::new("6")); + /// let lock = RetryingLockCollection::new(data); + /// + /// match lock.try_read(key) { + /// Some(mut guard) => { + /// assert_eq!(*guard.0, 5); + /// assert_eq!(*guard.1, "6"); + /// }, + /// None => unreachable!(), + /// }; + /// + /// ``` pub fn try_read<'g, 'key: 'g, Key: Keyable + 'key>( &'g self, key: Key, @@ -343,6 +538,22 @@ impl RetryingLockCollection { }) } + /// Unlocks the underlying lockable data type, returning the key that's + /// associated with it. + /// + /// # Examples + /// + /// ``` + /// use happylock::{RwLock, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = (RwLock::new(0), RwLock::new("")); + /// let lock = RetryingLockCollection::new(data); + /// + /// let mut guard = lock.read(key); + /// let key = RetryingLockCollection::<(RwLock, RwLock<&str>)>::unlock_read(guard); + /// ``` pub fn unlock_read<'key, Key: Keyable + 'key>( guard: LockGuard<'key, L::ReadGuard<'_>, Key>, ) -> Key { @@ -356,6 +567,23 @@ where &'a L: IntoIterator, { /// Returns an iterator over references to each value in the collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(26), Mutex::new(1)]; + /// let lock = RetryingLockCollection::new(data); + /// + /// let mut iter = lock.iter(); + /// let mutex = iter.next().unwrap(); + /// let guard = mutex.lock(key); + /// + /// assert_eq!(*guard, 26); + /// ``` #[must_use] pub fn iter(&'a self) -> <&'a L as IntoIterator>::IntoIter { self.into_iter() @@ -368,6 +596,22 @@ where { /// Returns an iterator over mutable references to each value in the /// collection. + /// + /// # Examples + /// + /// ``` + /// use happylock::{Mutex, ThreadKey}; + /// use happylock::collection::RetryingLockCollection; + /// + /// let key = ThreadKey::get().unwrap(); + /// let data = [Mutex::new(26), Mutex::new(1)]; + /// let mut lock = RetryingLockCollection::new(data); + /// + /// let mut iter = lock.iter_mut(); + /// let mutex = iter.next().unwrap(); + /// + /// assert_eq!(*mutex.as_mut(), 26); + /// ``` #[must_use] pub fn iter_mut(&'a mut self) -> <&'a mut L as IntoIterator>::IntoIter { self.into_iter() diff --git a/src/lockable.rs b/src/lockable.rs index 2f98d3a..6b9c7c6 100644 --- a/src/lockable.rs +++ b/src/lockable.rs @@ -311,6 +311,8 @@ unsafe impl Lockable for &T { } } +unsafe impl Sharable for &T {} + unsafe impl Lockable for &mut T { type Guard<'g> = T::Guard<'g> where Self: 'g; @@ -329,323 +331,43 @@ unsafe impl Lockable for &mut T { } } -unsafe impl OwnedLockable for &mut T {} - -unsafe impl Lockable for (A,) { - type Guard<'g> = (A::Guard<'g>,) where Self: 'g; - - type ReadGuard<'g> = (A::ReadGuard<'g>,) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - (self.0.guard(),) - } - - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - (self.0.read_guard(),) - } -} - -unsafe impl Lockable for (A, B) { - type Guard<'g> = (A::Guard<'g>, B::Guard<'g>) where Self: 'g; - - type ReadGuard<'g> = (A::ReadGuard<'g>, B::ReadGuard<'g>) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - self.1.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - (self.0.guard(), self.1.guard()) - } - - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - (self.0.read_guard(), self.1.read_guard()) - } -} - -unsafe impl Lockable for (A, B, C) { - type Guard<'g> = (A::Guard<'g>, B::Guard<'g>, C::Guard<'g>) where Self: 'g; - - type ReadGuard<'g> = (A::ReadGuard<'g>, B::ReadGuard<'g>, C::ReadGuard<'g>) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - self.1.get_ptrs(ptrs); - self.2.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - (self.0.guard(), self.1.guard(), self.2.guard()) - } - - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - ( - self.0.read_guard(), - self.1.read_guard(), - self.2.read_guard(), - ) - } -} - -unsafe impl Lockable for (A, B, C, D) { - type Guard<'g> = (A::Guard<'g>, B::Guard<'g>, C::Guard<'g>, D::Guard<'g>) where Self: 'g; - - type ReadGuard<'g> = ( - A::ReadGuard<'g>, - B::ReadGuard<'g>, - C::ReadGuard<'g>, - D::ReadGuard<'g>, - ) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - self.1.get_ptrs(ptrs); - self.2.get_ptrs(ptrs); - self.3.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - ( - self.0.guard(), - self.1.guard(), - self.2.guard(), - self.3.guard(), - ) - } - - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - ( - self.0.read_guard(), - self.1.read_guard(), - self.2.read_guard(), - self.3.read_guard(), - ) - } -} - -unsafe impl Lockable - for (A, B, C, D, E) -{ - type Guard<'g> = ( - A::Guard<'g>, - B::Guard<'g>, - C::Guard<'g>, - D::Guard<'g>, - E::Guard<'g>, - ) where Self: 'g; - - type ReadGuard<'g> = ( - A::ReadGuard<'g>, - B::ReadGuard<'g>, - C::ReadGuard<'g>, - D::ReadGuard<'g>, - E::ReadGuard<'g>, - ) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - self.1.get_ptrs(ptrs); - self.2.get_ptrs(ptrs); - self.3.get_ptrs(ptrs); - self.4.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - ( - self.0.guard(), - self.1.guard(), - self.2.guard(), - self.3.guard(), - self.4.guard(), - ) - } - - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - ( - self.0.read_guard(), - self.1.read_guard(), - self.2.read_guard(), - self.3.read_guard(), - self.4.read_guard(), - ) - } -} - -unsafe impl Lockable - for (A, B, C, D, E, F) -{ - type Guard<'g> = ( - A::Guard<'g>, - B::Guard<'g>, - C::Guard<'g>, - D::Guard<'g>, - E::Guard<'g>, - F::Guard<'g>, - ) where Self: 'g; - - type ReadGuard<'g> = ( - A::ReadGuard<'g>, - B::ReadGuard<'g>, - C::ReadGuard<'g>, - D::ReadGuard<'g>, - E::ReadGuard<'g>, - F::ReadGuard<'g>, - ) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - self.1.get_ptrs(ptrs); - self.2.get_ptrs(ptrs); - self.3.get_ptrs(ptrs); - self.4.get_ptrs(ptrs); - self.5.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - ( - self.0.guard(), - self.1.guard(), - self.2.guard(), - self.3.guard(), - self.4.guard(), - self.5.guard(), - ) - } +unsafe impl Sharable for &mut T {} - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - ( - self.0.read_guard(), - self.1.read_guard(), - self.2.read_guard(), - self.3.read_guard(), - self.4.read_guard(), - self.5.read_guard(), - ) - } -} - -unsafe impl - Lockable for (A, B, C, D, E, F, G) -{ - type Guard<'g> = ( - A::Guard<'g>, - B::Guard<'g>, - C::Guard<'g>, - D::Guard<'g>, - E::Guard<'g>, - F::Guard<'g>, - G::Guard<'g>, - ) where Self: 'g; - - type ReadGuard<'g> = ( - A::ReadGuard<'g>, - B::ReadGuard<'g>, - C::ReadGuard<'g>, - D::ReadGuard<'g>, - E::ReadGuard<'g>, - F::ReadGuard<'g>, - G::ReadGuard<'g>, - ) where Self: 'g; - - fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { - self.0.get_ptrs(ptrs); - self.1.get_ptrs(ptrs); - self.2.get_ptrs(ptrs); - self.3.get_ptrs(ptrs); - self.4.get_ptrs(ptrs); - self.5.get_ptrs(ptrs); - self.6.get_ptrs(ptrs); - } - - unsafe fn guard(&self) -> Self::Guard<'_> { - ( - self.0.guard(), - self.1.guard(), - self.2.guard(), - self.3.guard(), - self.4.guard(), - self.5.guard(), - self.6.guard(), - ) - } - - unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { - ( - self.0.read_guard(), - self.1.read_guard(), - self.2.read_guard(), - self.3.read_guard(), - self.4.read_guard(), - self.5.read_guard(), - self.6.read_guard(), - ) - } -} - -unsafe impl Sharable for (A,) {} -unsafe impl Sharable for (A, B) {} - -unsafe impl Sharable for (A, B, C) {} - -unsafe impl Sharable for (A, B, C, D) {} +unsafe impl OwnedLockable for &mut T {} -unsafe impl Sharable - for (A, B, C, D, E) -{ -} +macro_rules! tuple_impls { + ($($generic:ident)*, $($value:tt)*) => { + unsafe impl<$($generic: Lockable,)*> Lockable for ($($generic,)*) { + type Guard<'g> = ($($generic::Guard<'g>,)*) where Self: 'g; -unsafe impl Sharable - for (A, B, C, D, E, F) -{ -} + type ReadGuard<'g> = ($($generic::ReadGuard<'g>,)*) where Self: 'g; -unsafe impl - Sharable for (A, B, C, D, E, F, G) -{ -} + fn get_ptrs<'a>(&'a self, ptrs: &mut Vec<&'a dyn RawLock>) { + self.0.get_ptrs(ptrs); + } -unsafe impl OwnedLockable for (A,) {} -unsafe impl OwnedLockable for (A, B) {} + unsafe fn guard(&self) -> Self::Guard<'_> { + ($(self.$value.guard(),)*) + } -unsafe impl OwnedLockable for (A, B, C) {} + unsafe fn read_guard(&self) -> Self::ReadGuard<'_> { + ($(self.$value.read_guard(),)*) + } + } -unsafe impl OwnedLockable - for (A, B, C, D) -{ -} + unsafe impl<$($generic: Sharable,)*> Sharable for ($($generic,)*) {} -unsafe impl - OwnedLockable for (A, B, C, D, E) -{ + unsafe impl<$($generic: OwnedLockable,)*> OwnedLockable for ($($generic,)*) {} + }; } -unsafe impl< - A: OwnedLockable, - B: OwnedLockable, - C: OwnedLockable, - D: OwnedLockable, - E: OwnedLockable, - F: OwnedLockable, - > OwnedLockable for (A, B, C, D, E, F) -{ -} - -unsafe impl< - A: OwnedLockable, - B: OwnedLockable, - C: OwnedLockable, - D: OwnedLockable, - E: OwnedLockable, - F: OwnedLockable, - G: OwnedLockable, - > OwnedLockable for (A, B, C, D, E, F, G) -{ -} +tuple_impls!(A, 0); +tuple_impls!(A B, 0 1); +tuple_impls!(A B C, 0 1 2); +tuple_impls!(A B C D, 0 1 2 3); +tuple_impls!(A B C D E, 0 1 2 3 4); +tuple_impls!(A B C D E F, 0 1 2 3 4 5); +tuple_impls!(A B C D E F G, 0 1 2 3 4 5 6); unsafe impl Lockable for [T; N] { type Guard<'g> = [T::Guard<'g>; N] where Self: 'g; diff --git a/src/mutex/guard.rs b/src/mutex/guard.rs index 35fe1f2..9e8e2e6 100644 --- a/src/mutex/guard.rs +++ b/src/mutex/guard.rs @@ -61,7 +61,9 @@ impl<'a, T: ?Sized + 'a, R: RawMutex> AsMut for MutexRef<'a, T, R> { } impl<'a, T: ?Sized + 'a, R: RawMutex> MutexRef<'a, T, R> { - pub unsafe fn new(mutex: &'a Mutex) -> Self { + /// Creates a reference to the underlying data of a mutex without + /// attempting to lock it or take ownership of the key. + pub(crate) unsafe fn new(mutex: &'a Mutex) -> Self { Self(mutex, PhantomData) } } diff --git a/src/rwlock/read_guard.rs b/src/rwlock/read_guard.rs index da3d101..e46078c 100644 --- a/src/rwlock/read_guard.rs +++ b/src/rwlock/read_guard.rs @@ -46,7 +46,9 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Drop for RwLockReadRef<'a, T, R> { } impl<'a, T: ?Sized + 'a, R: RawRwLock> RwLockReadRef<'a, T, R> { - pub unsafe fn new(mutex: &'a RwLock) -> Self { + /// Creates an immutable reference for the underlying data of an [`RwLock`] + /// without locking it or taking ownership of the key. + pub(crate) unsafe fn new(mutex: &'a RwLock) -> Self { Self(mutex, PhantomData) } } diff --git a/src/rwlock/read_lock.rs b/src/rwlock/read_lock.rs index 29042b5..4f2bc86 100644 --- a/src/rwlock/read_lock.rs +++ b/src/rwlock/read_lock.rs @@ -36,7 +36,7 @@ impl<'l, T, R> From<&'l RwLock> for ReadLock<'l, T, R> { impl<'l, T: ?Sized, R> AsRef> for ReadLock<'l, T, R> { fn as_ref(&self) -> &RwLock { - &self.0 + self.0 } } diff --git a/src/rwlock/write_guard.rs b/src/rwlock/write_guard.rs index c8dd58b..ec622d7 100644 --- a/src/rwlock/write_guard.rs +++ b/src/rwlock/write_guard.rs @@ -48,7 +48,9 @@ impl<'a, T: ?Sized + 'a, R: RawRwLock> Drop for RwLockWriteRef<'a, T, R> { } impl<'a, T: ?Sized + 'a, R: RawRwLock> RwLockWriteRef<'a, T, R> { - pub unsafe fn new(mutex: &'a RwLock) -> Self { + /// Creates a reference to the underlying data of an [`RwLock`] without + /// locking or taking ownership of the key. + pub(crate) unsafe fn new(mutex: &'a RwLock) -> Self { Self(mutex, PhantomData) } } -- cgit v1.2.3