summaryrefslogtreecommitdiff
path: root/happylock.md
diff options
context:
space:
mode:
authorBotahamec <botahamec@outlook.com>2025-01-12 15:04:01 -0500
committerBotahamec <botahamec@outlook.com>2025-01-12 15:04:01 -0500
commit280a61ad7b74019c7aad8b7306a0dd7cfb11359c (patch)
tree40d97d4e183c1bc551194944876b099d875f361d /happylock.md
parentbf95904b3532a9175a7bfa14a3f216abbd15ee98 (diff)
More unit tests
Diffstat (limited to 'happylock.md')
-rw-r--r--happylock.md205
1 files changed, 152 insertions, 53 deletions
diff --git a/happylock.md b/happylock.md
index 0e250fe..973b819 100644
--- a/happylock.md
+++ b/happylock.md
@@ -2,6 +2,7 @@
marp: true
theme: gaia
class: invert
+author: Mica White
---
<!-- _class: lead invert -->
@@ -112,7 +113,7 @@ 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
+ // ThreadKey is not Send, Copy, or Clone
let key = ThreadKey::get().unwrap();
let mutex = Mutex::new(10);
@@ -153,6 +154,8 @@ fn main() {
}
```
+This `LockCollection` can be implemented simply by releasing the currently acquired locks and retrying on failure
+
---
## The Lockable API
@@ -266,7 +269,17 @@ Time Complexity: O(nlogn)
## 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.
+Although this library is able to successfully prevent deadlocks, livelocks may still be an issue.
+
+1. Thread 1 locks mutex 1
+2. Thread 2 locks mutex 2
+3. Thread 1 tries to lock mutex 2 and fails
+4. Thread 2 tries to lock mutex 1 and fails
+5. Thread 1 releases mutex 1
+6. Thread 2 releases mutex 2
+7. Repeat
+
+This pattern will probably end eventually, but we should really avoid it, for performance reasons.
---
@@ -384,8 +397,8 @@ This is what we were trying to avoid earlier
This is what I used in HappyLock 0.1:
```rust
-struct ReadLock<'a, T(&'a RwLock<T>);
-struct WriteLock<'a, T(&'a RwLock<T>);
+struct ReadLock<'a, T>(&'a RwLock<T>);
+struct WriteLock<'a, T>(&'a RwLock<T>);
```
**Problem:** This can't be used inside of an `OwnedLockCollection`
@@ -413,7 +426,7 @@ unsafe trait Lockable {
---
-## Not every lock can be read doe
+## Not every lock can be read tho
```rust
// This trait is used to indicate that reading is actually useful
@@ -432,25 +445,6 @@ impl<L: Sharable> OwnedLockable<L> {
---
-## 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)
-
----
-
-<!--_class: invert lead -->
-
-## What's next?
-
----
-
## Poisoning
```rust
@@ -473,50 +467,55 @@ Allows: `Poisonable<LockCollection>` and `LockCollection<Poisonable>`
---
-## OS Locks
+# `LockableGetMut` and `LockableIntoInner`
-- Using `parking_lot` makes the binary size much larger
-- Unfortunately, it's impossible to implement `RawLock` on the standard library lock primitives
-- Creating a new crate based on a fork of the standard library is hard
-- Solution: create a new library (`sys_locks`), which exposes raw locks from the operating system
-- This is more complicated than you might think
-
----
+```rust
+fn Mutex::<T>::get_mut(&mut self) -> &mut T // already exists in std
+// this is safe because a mutable reference means nobody else can access the lock
-## Expanding Cyclic Wait
+trait LockableGetMut: Lockable {
+ type Inner<'a>;
-> ... sometimes you need to lock an object to read its value and determine what should be locked next... is there a way to address it?
+ fn get_mut(&mut self) -> Self::Inner<'_>
+}
-```rust
-let guard = m1.lock(key);
-if *guard == true {
- let key = Mutex::unlock(m);
- let data = [&m1, &m2];
- let collection = LockCollection::try_new(data).unwrap();
- let guard = collection.lock(key);
+impl<A: LockableGetMut, B: LockableGetMut> LockableGetMut for (A, B) {
+ type Inner = (A::Inner<'a>, B::Inner<'b>);
- // m1 might no longer be true here...
+ fn get_mut(&mut self) -> Self::Inner<'_> {
+ (self.0.get_mut(), self.1.get_mut())
+ }
}
```
---
-## What I Really Want
+## Missing Features
-```txt
-ordered locks: m1, m2, m3
+- `Condvar`/`Barrier`
+- `OnceLock` or `LazyLock`
+- Standard Library Backend
+- Support for `no_std`
+- Convenience methods: `lock_swap`, `lock_set`?
+- `try_lock_swap` doesn't need a `ThreadKey`
+- Going further: `LockCell` API (preemptive allocation)
-if m1 is true
- lock m2 and keep m1 locked
-else
- skip m2 and lock m3
-```
+---
-We can specify lock orders using `OwnedLockCollection`
+<!--_class: invert lead -->
+
+## What's next?
+
+---
-Then we need an iterator over the collection to keep that ordering
-This will be hard to do with tuples (but might not be impossible)
+## OS Locks
+
+- Using `parking_lot` makes the binary size much larger
+- Unfortunately, it's impossible to implement `RawLock` on the standard library lock primitives
+- Creating a new crate based on a fork of the standard library is hard
+- Solution: create a new library (`sys_locks`), which exposes raw locks from the operating system
+- This is more complicated than you might think
---
@@ -618,6 +617,106 @@ A `Readonly` collection cannot be exclusively locked.
- can these even deadlock?
---
+## Expanding Cyclic Wait
+
+> ... sometimes you need to lock an object to read its value and determine what should be locked next... is there a way to address it?
+
+```rust
+let guard = m1.lock(key);
+if *guard == true {
+ let key = Mutex::unlock(m);
+ let data = [&m1, &m2];
+ let collection = LockCollection::try_new(data).unwrap();
+ let guard = collection.lock(key);
+
+ // m1 might no longer be true here...
+}
+```
+
+---
+
+## What I Really Want
+
+```txt
+ordered locks: m1, m2, m3
+
+if m1 is true
+ lock m2 and keep m1 locked
+else
+ skip m2 and lock m3
+```
+
+We can specify lock orders using `OwnedLockCollection`
+
+Then we need an iterator over the collection to keep that ordering
+
+This will be hard to do with tuples (but is not be impossible)
+
+---
+
+## Something like this
+
+```rust
+let key = ThreadKey::get().unwrap();
+let collection: OwnedLockCollection<(Vec<i32>, Vec<String>);
+let iterator: LockIterator<(Vec<i32>, Vec<String>)> = collection.locking_iter(key);
+let (guard, next: LockIterator<Vec<String>>) = collection.next();
+
+unsafe trait IntoLockIterator: Lockable {
+ type Next: Lockable;
+ type Rest;
+
+ unsafe fn next(&self) -> Self::Next; // must be called before `rest`
+ fn rest(&self) -> Self::Rest;
+}
+
+unsafe impl<A: Lockable, B: Lockable> IntoLockIterator for (A, B) {
+ type Next = A;
+ type Rest = B;
+
+ unsafe fn next(&self) -> Self::Next { self.0 }
+
+ unsafe fn rest(&self) -> Self::Rest { self.1 }
+}
+```
+
+---
+
+## Here are the helper functions we'll need
+
+```rust
+struct LockIterator<Current: IntoLockIterator, Rest: IntoLockIterator = ()>;
+
+impl<Current, Rest> LockIterator<Current, Rest> {
+ // locks the next item and moves on
+ fn next(self) -> (Current::Next::Guard, LockIterator<Current::Rest>);
+
+ // moves on without locking anything
+ fn skip(self) -> LockIterator<Current::Rest>;
+
+ // steps into the next item, allowing parts of it to be locked
+ // For example, if i have LockIterator<(Vec<String>, Vec<i32>)>, but only
+ // want to lock parts of the first Vec, then I can step into it,
+ // locking what i need to, and then exit.
+ // This is the first use of LockIterator's second generic parameter
+ fn step_into(self) -> LockIterator<Current::Next, Rest=Current::Rest>;
+
+ // Once I'm done with my step_into, I can leave and move on
+ fn exit(self) -> LockIterator<Rest>;
+}
+```
+
+---
+
+## A Quick Problem with this Approach
+
+We're going to be returning a lot of guards.
+
+The `ThreadKey` is held by the `LockIterator`.
+
+**How do we ensure that the `ThreadKey` is not used again until all of the guards are dropped?**
+
+---
<!--_class: invert lead -->