summaryrefslogtreecommitdiff
path: root/src/blog/how-happylock-works.kuht
blob: c1cf6315b47f95d0695c82f6b2d072e12d470715 (plain)
<import "base.kuht" as "base" />

<head>
	<title>How HappyLock Works</title>
	<meta name="description" content="Recently, I released version 0.3 of a Rust library which prevents deadlocks at compile-time. I want to explain what I changed, and why it works" />
</head>

<body>

<article>

<h1>How HappyLock Works</h1>

<p>
	Recently, I released version 0.3 of my <a href="https://www.lib.rs/happylock">HappyLock</a> crate
	on crates.io. In this blog post, I wanted to explain what I changed, and why it works.
</p>

<h2>Background</h2>

<p>
	There are four conditions necessary for a deadlock to occur. You only need to prevent one of them
	in order to prevent all deadlocks:
</p>

<ol>
	<li>Mutual exclusion</li>
	<li>Non-preemptive allocation</li>
	<li>Circular wait</li>
	<li>Partial allocation</li>
</ol>

<p>Let's go through each one, and see what we can do.</p>

<h3>Mutual exclusion</h3>

<p>
	This doesn't make all that much sense to prevent. We could provide some sort of
	<code>Readonly&lt;T></code> type, but Rust already has <code>&amp;T</code> and
	<code>Arc&lt;T></code>. So this wouldn't be very useful.
</p>

<h3>Non-preemptive allocation</h3>

<p>
	Preventing this would mean that the language can decide to take away your access to a piece of
	data at any time. Not only would this be incredibly difficult to pull off, it would also just
	annoying for the programmer to deal with. Something like this would need to happen:
</p>

<pre>
let mutex = Mutex::new(5);
let mut_ref = &amp;mutex;
let th = thread::spawn(|| {
	let number = mut_ref.lock();
	*number = 6;
});

let number = mut_ref.lock();
th.join(); // preempts the lock on mut_ref
println!("{}", number); // oops, we can't use number anymore
</pre>

<p>
	We could have some sort of <code>LockCell</code> type, which releases the lock immediately after
	performing some action, but there are often times where you need to make sure nothing else can
	mutate the value for a section of code. So this won't always work.
</p>

<h3>Circular wait</h3>

<p>
	From what I can tell, most attempts to prevent deadlock go through this route. The idea is what
	every lock must be acquired <i>in the same order</i>. So if you lock <code>l1</code> and then
	<code>l2</code> on one thread, then you can't do it in the opposite order on the other thread.
	This is an interesting approach, but there's no way to enforce it in the language. Some people
	have resorted to using macros for this, such as in the recently published
	<a href="https://www.lib.rs/deadlocker">deadlocker crate</a> (which is a very confusing name).
	But an attempt to make this happen at compile-time won't be dynamic enough for the general case.
</p>

<p>Keep an eye on this one, because we will make use of it later.</p>

<h3>Partial allocation</h3>

<p>
	This is the hero we've been waiting for. To prevent this, we need to enforce total allocation. So if
	you want to lock a new mutex, you must first release the locks you've already acquired, and then
	acquire a new set of locks. Preventing this is possible in Rust, and less possible in languages that
	don't have a borrow checker. Let me explain how we'll use the borrow checker.
</p>

<h2>Exploration</h2>

<p>
	Say we were making mutexes for an operating system. How would we enforce total allocation? We could
	simply give an error if someone acquires a new set of locks without releasing the already acquired
	locks. It'd also mean we'd have to check for an error every time we acquire a new set of locks.
</p>

<pre>
mutex_t m1 = new_mutex();
mutex_t m2 = new_mutex();

mutex_t mutices[] = { m1, m2 };
if (lock_mutexes(mutices, 2)) {
	// oh noes!
}

// now we can use the data
</pre>

<p>But we're not C. We have technology! And our technology is the borrow checker.</p>

<h3>Borrow Checker Rules</h3>

<p>For those of you who don't know, here's a quick rundown of the borrow checker rules:</p>

<ol>
	<li>A value may not be moved while references to it exist.</li>
	<li>Only one mutable reference to a value may exist at a time.</li>
	<li>A mutable reference and immutable references, must not exist at the same time.</li>
</ol>

<p>As a quick example of these rules:</p>

<pre>
let s = String::new("Hello, world!");
let r1 = &amp;s;
let r2 = &amp;s; // this is allowed because these references aren't mutable
let mr = &mut s; // illegal: rule #3
drop(s); // also illegal: rule #1
println!("{r1} {r2}");
</pre>

<h2>A Naive implementation</h2>

<p>
	So here's the plan: We'll make a new type called <code>ThreadKey</code>. This type cannot be
	cloned, copied, sent to another thread, or mutably referenced from another thread. The Rust type
	system is able to enforce all of those things.
</p>

<pre>
// no traits are derived, especially not Clone or Copy
pub struct ThreadKey {
	phantom: PhantomData&lt;*const ()>, // !Send and !Sync
}
</pre>

<p>
	Then, we can require that when locking a mutex (or a read-write lock), a <code>ThreadKey</code>,
	or a <code>&amp;mut ThreadKey</code>, must be given.
</p>

<pre>
pub fn lock&lt;'s, 'k: 's, Key: Keyable>(&'s self, key: Key) -> MutexGuard&lt;'_, 'k, T, Key, R>
</pre>

<p>
	Wow! That's a lot of lifetimes and generics. We'll worry about those later. For now, let's show
	how this works.
</p>

<pre>
use happylock::{ThreadKey, Mutex};

fn main() {
	// each thread can only have one thread key
	// the call to unwrap panics if this thread already got its key
	// 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);

	// we no longer have the thread key, so we can't lock anything else anymore

	println!("{}", *guard);
}
</pre>

<p>
	I recently learned that I wasn't the only person who thought of this. Over a year ago, Adrian
	Taylor posted on Medium,
	<a href="https://medium.com/@adetaylor/can-the-rust-type-system-prevent-deadlocks-9ae6e4123037">
		Can the Rust type system prevent deadlocks?</a>.
	I was completely unaware of this when I published the crate, despite my search for other
	implementations of the idea. He also worked on getting inner mutexes to work, which is currently
	impossible in HappyLock. He didn't attempt to do something more obvious though, which is locking
	more than one mutex at a time. He also never considered the possibility of using a
	<code>&amp; mut MutexPermissionToken</code> to make the code more ergonomic.
</p>

<p>Speaking of which, how do we lock more than one mutex at a time?</p>

<h3>Lock Collections</h3>

<p>
	It's actually quite simply, we just lock a collection of things. We can have a type, called
	<code>LockCollection</code>, which takes a collection of locks, and locks them all at the same time.
	Then, it'll return a <code>LockGuard</code> for the collection.
</p>

<p>
	There are different collections we could try to lock, including <code>Vec&lt;T></code>,
	<code>(A, B)</code>, and <code>(Vec&lt;A>, Vec&lt;B>)</code>, so this'll have to be generic. To
	enable this, we'll create a trait called <code>Lockable</code>.
</p>

<p>This isn't the current implementation in HappyLock, but here's a proof-of-concept:</p>

<pre>
unsafe trait Lockable {
	type Guard&lt;'a>;

	unsafe fn lock&lt;'a>(&amp;self) -> Self::Guard&lt;'a>;

	unsafe fn try_lock&lt;'a>(&amp;self) -> Option&lt;Self::Guard&lt;'a>>;
}
</pre>

<p>Then, we just need to implement on every collection that could hold a lock.</p>

<pre>
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;
}
</pre>

<p>That seems to work pretty well.</p>

<h3>Duplicate Locks</h3>

<p>Let's try something:</p>

<pre>
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);
}
</pre>

<p>
	The problem is that we passed the same lock in twice. That'll be a problem if we require it to be
	locked twice. The good news is, this code doesn't compile, because <code>LockCollection::new</code>
	actually requires a different trait that I haven't mentioned.
</p>

<pre>
// not implemented for &impl Lockable
// ergo: the values within are guaranteed to be unique
unsafe trait OwnedLockable: Lockable {}
</pre>

<p>
	Let me elaborate on this. If the value is owned, then there's no way to pass it twice into the same
	collection. The same is true for mutable references. So we can safely lock an owned lock without
	checking for duplicates.
</p>

<p>
	But sometimes, it's useful to lock a referenced mutex. For these, we'll need a runtime check for
	duplicates. So we'll need to add another method to our <code>Lockable</code> trait.
</p>

<pre>
unsafe trait Lockable {
	// ... snip ...

	fn get_ptrs(&amp;self) -> Vec&lt;usize>;
}
</pre>

<p>
	Then we can check for duplicate pointers within a collection. My first implementation of that check
	was a brute-force O(n²) check. When I posted about this on Reddit, people were quick to point out
	that this could be made O(nlogn) by sorting the pointers, then checking if the same pointer appeared
	twice in a row.
</p>

<pre>
fn contains_duplicates&lt;L: Lockable>(data: L) -> bool {
	let mut pointers = data.get_ptrs();
	pointers.sort_unstable();
	pointers.windows(2).any(|w| w[0] == w[1])
}
</pre>

<p>Of course, you could make this an O(n) check by using a <code>HashSet</code> too.</p>

<p>Now we need a new constructor for <code>LockCollection</code>.</p>

<pre>
impl&lt;L: Lockable> LockCollection&lt;L> {
	pub fn try_new(data: L) -> Option&lt;Self> {
		let ptrs = data.get_ptrs();
		if contains_duplicates(&ptrs) {
			return None;
		}

		Some(Self { data })
	}
}
</pre>

<h2>The Actual Implementation</h2>

<p>
	Although we have now successfully prevented deadlocks,
	<a href="https://en.wikipedia.org/wiki/Deadlock#Livelock">livelocking</a> can still be an issue.
</p>

<p>
	The first implementation of <code>LockCollection</code> would try to lock everything in order. If
	one of the locks blocked, then it would release everything it had and try again. It would keep
	retrying until it successfully locked everything in a row. This resulted in a lot of wasted work. It
	also made the <code>LockCollection</code> start to resemble a spinlock. This also made the
	collection prone to livelocking.
</p>

<p>
	Imagine this scenario with two threads trying to lock a set of mutexes, but in a different order:
</p>

<ol>
	<li>Thread 1 locks mutex 1</li>
	<li>Thread 2 locks mutex 2</li>
	<li>Thread 1 tries to lock mutex 2 and fails</li>
	<li>Thread 2 tries to lock mutex 1 and fails</li>
	<li>Thread 1 releases mutex 1</li>
	<li>Thread 2 releases mutex 2</li>
	<li>Repeat</li>
</ol>

<p>
	In practice, this loop would probably end eventually, but it'd be nice if we could prevent it
	altogether. You could avoid this by locking everything in the same order. But if we could do that,
	then we would've prevented cyclic wait from before, which would have avoided this whole problem.
</p>

<h3>Cyclic wait</h3>

<p>
	Remember when I said we would make use of cyclic wait later? This is where we'll do this. If you
	recall from earlier, we already sorted the mutex pointers by their memory address. What if we locked
	them in that order? This was suggested by many people on Reddit, but it took a while to work out
	exactly how to do this. I can't sort a tuple. So we'll need to lock them in a different order than
	the one they're returned in. This will require a large change to the existing <code>Lockable</code>
	trait. Let's see what we can do.
</p>

<pre>
unsafe trait Lock {
	unsafe fn lock(&amp;self);
	unsafe fn try_lock(&amp;self) -> bool;
	unsafe fn unlock(&amp;self);
}

unsafe trait Lockable {
	type Guard&lt;'g>;
	fn get_locks&lt;'a>(&amp;'a self, &amp;mut Vec&lt;&amp;'a dyn Lock>);
	unsafe fn guard&lt;'g>(&amp;'g self) -> Self::Guard&lt;'g>;
}
</pre>

<p>
	<code>Lock</code> is implemented on <code>Mutex</code> and <code>RwLock</code>. <code>Lockable</code>
	is implemented on the same types as before. These traits are even more unsafe than they were before,
	now that they can obtain a guard without locking it first. So, we'll just need to be very careful.
</p>

<p>Now let's try making our new <code>LockCollection</code></p>

<pre>
struct LockCollection&lt;L> {
	data: L,
	locks: Vec&lt;&amp;'self.data dyn Lock>
	// wait, this is a self-referential struct
}
</pre>

<p>
	Ooh, boy. This is gonna be a problem. We could box the data, but sometimes we don't want to pay the
	price of memory allocation. We could try using a reference, but then we have an extra lifetime to
	deal with. It's also sometimes useful to have an owned lock collection. How are we going to do this
	without creating four lock collection types that do mostly the same thing?
</p>

<h3>We'll just create four Lock Collection Types that do mostly the same thing</h3>

<p>
	I'll cover each of the four types, but if you're looking for a sensible default, there's a type alias
	so that <code>LockCollection</code> points to <code>BoxedLockCollection</code>. It's slower and
	doesn't allow you to mutate the underlying collection (yet), but it works for most use cases.
</p>

<p>
	I'm pretty sure when this gets posted to Reddit, someone will tell me I could've avoided this
	somehow.
</p>

<h4><code>RefLockCollection</code></h4>

<pre>
pub struct RefLockCollection&lt;'a, L> {
	data: &amp;'a L,
	locks: Vec&lt;&amp;'a dyn RawLock>,
}
</pre>

<p>
	It needs to sort the locks by address. Yes, that applies even if the collection is owned. Otherwise
	the locks could be acquired in a different order. We're no longer releasing the locks when we can't
	acquire them all at once, so they need to be acquired in exactly that order. But it avoids one memory
	allocation.
</p>

<h4><code>BoxedLockCollection</code></h4>

<pre>
pub struct BoxedLockCollection&lt;L> {
	data: *const UnsafeCell&lt;L>,
	locks: Vec&lt;&amp;'static dyn RawLock>,
}
</pre>

<p>
	This is similar to <code>RefLockCollection</code>, but it allocates more memory. You may notice that
	there's no <code>Box</code> in this structure, despite the name. That's because <code>Box</code>
	needs to be a unique pointer. But because of the references in <code>locks</code>, it's not unique.
	It's <code>const</code> because of the existing references to the collection, meaning mutating the
	underlying collection would cause undefined behavior, especially if it means the locks move (such
	as when a <code>Vec</code> reallocates). It's a raw pointer, because deallocating the data requires
	a mutable pointer, and creating a mutable pointer from an immutable reference is undefined behavior.
	The <code>UnsafeCell</code> is used for a similar reason.
</p>

<p>
	This is actually the reason why this release is titled 0.3 and not 0.2. I released 0.2 and found
	these soundness bugs, so I had to make a fix. This was a breaking change, because you can't mutate
	the underlying collection anymore.
</p>

<h4><code>OwnedLockCollection</code></h4>

<pre>
pub struct OwnedLockCollection&lt;L> {
	data: L,
}
</pre>

<p>
	Since both <code>RefLockCollection</code> and <code>BoxedLockCollection</code> require sorting the
	locks, it'd be nice if we could sometimes avoid that for owned lock collections. If a set of locks is
	in an <code>OwnedLockCollection</code>, then it really cannot be used anywhere else. That means as
	long as references to a <code>OwnedLockCollection</code> always lock in the same order, then it'll
	never deadlock. That's the only order these particular locks can be locked in. So, we don't bother
	sorting and just lock them in the order they appear in for the collection.
</p>

<h4><code>RetryingLockCollection</code></h4>

<p>
	This is the collection that was used in Happylock 0.1. This does the releasing of all of the locks
	thing. The upside of this collection is that it can contain references, and still not sort the
	pointers. Duplicates are checked for by using a <code>HashSet</code>. Because of the lack of sorting,
	it can be fast in the following case, from the docs:
</p>

<blockquote>
	[...] when the first lock in the collection is always the first in any
	collection, and the other locks in the collection are always locked after
	that first lock is acquired. This means that as soon as it is locked, there
	will be no need to unlock it later on subsequent lock attempts, because
	they will always succeed.
</blockquote>

<h3>RwLocks</h3>

<p>
	It doesn't take a genius to realize that my lock collection API is not friendly to read-write locks.
	In HappyLock 0.1, I had <code>ReadLock</code> and <code>WriteLock</code> wrappers for
	<code>RwLock</code> to allow reads in collections.
</p>

<pre>
struct ReadLock&lt;'a, T(&amp;'a RwLock&lt;T>);
struct WriteLock&lt;'a, T(&amp;'a RwLock&lt;T>);
</pre>

<p>
	This worked fine at the time, but I quickly realized that this would not work for an
	<code>OwnedLockCollection</code>. To solve this problem, I made more changes to the
	<code>Lockable</code> API.
</p>

<pre>
unsafe trait Lock {
	// ...
	unsafe fn read(&amp;self);
	unsafe fn try_read(&amp;self) -> bool;
	unsafe fn unlock_read(&amp;self);
}

unsafe trait Lockable {
	// ...
	type ReadGuard&lt;'g>;
	unsafe fn read_guard&lt;'g>(&amp;'g self) -> Self::ReadGuard&lt;'g>
}

// A NEW TRAIT
unsafe trait Sharable: Lockable {}
</pre>

<p>
	The <code>Sharable</code> trait is implemented on collections that only contain readable locks.
	All of the lock collection types then have functions for when their locks are all sharable. That
	allows us to grant read-access in <code>OwnedLockCollection</code>s.
</p>

<p>After I was done with that, I updated the documentation and published the new version.</p>

<h2>Performance</h2>

<p>
	<code>ThreadKey</code> is a zero-cost abstraction. It takes zero space at runtime. The only code is
	checking to see if the key has already been acquired for a thread. That is a static,
	lazily-initialized, thread local, boolean check and update, so it's fairly fast.
</p>

<p>
	I used <code>lock_api</code> as backends for the <code>Mutex</code> and <code>RwLock</code> types.
	By default, this library acts as a thin wrapper around <code>parking_lot</code>, so it will be
	roughly the same speed as <code>parking_lot</code>. There's an optional feature that can be enabled
	to use <code>spin</code> as well, and you can import your own raw locks if you wish. The standard
	library locks cannot be used, because it's impossible to implement the <code>lock_api</code> traits
	on them. If someone adds a <code>Mutex::force_unlock</code> function to the standard library, then I
	can add that support, and I'll even make it the default. For now, we have to deal with the larger
	binary size that's given by <code>parking_lot</code>.
</p>

<p>
	The only place where performance could be an issue is in the lock collection types. The performance
	qualities of each type have already been discussed. But to summarize, only the
	<code>OwnedLockCollection</code> is zero-cost. Both <code>RefLockCollection</code> and
	<code>BoxedLockCollection</code> allocate memory and sort the lock pointers, and sometimes they'll
	even need to check for duplicates within the pointers. All of this work is done when the collection
	is created though, so at least you don't need to worry about it after that. On the other hand, the
	<code>RetryingLockCollection</code>, when locking, will waste time unlocking and relocking unless the
	following condition is met:
</p>

<blockquote>
	[...] when the first lock in the collection is always the first in any collection, and the other
	locks in the collection are always locked after that first lock is acquired.
</blockquote>

<p><code>RetryingLockCollection</code> also checks for duplicates using a <code>HashSet</code>.</p>

<p>In summary, it's pretty fast, but choose the correct lock collection constructor carefully.</p>

<h2>Future Work</h2>

<p>
	This is summarized on the GitHub repo, but I'll try to go into more depth on my plans for the future
	of this library.
</p>

<h3>Poisoning</h3>

<p>
	Personally, I'm not a big fan of mutex poisoning, but I can see why someone would want to use it. I
	plan on adding it to Happylock soon, by having a <code>Poisonable</code> wrapper around
	<code>Lock</code> types. And if I implement <code>Lock</code> on the lock collection types, then
	it'll even be able to wrap those, which might be useful.
</p>

<h3>OS Locks</h3>

<p>
	I originally thought of this as being a must-have for the next version of HappyLock, but after trying
	it for a bit, I decided to hold off on it. As I mentioned before, there is a binary size cost to
	using <code>parking_lot</code>. We could save memory by using the locks that are built into the
	operating system, which is what the standard library does. It would be very convenient if I could
	just use the standard library as a backend for HappyLock.
</p>

<p>
	Unfortunately, there's a reason why the standard library's locks do not implement
	<code>lock_api</code>'s traits. There's just no way to unlock one of those mutexes without holding
	onto the guard. If we were to put the guard inside of the mutex, and then drop it when unlocking,
	that would make it impossible to share the lock across threads, since the guard isn't
	<code>Send</code>.
</p>

<p>
	There are raw mutexes used in the underlying implementation of the standard library, but those aren't
	public. I've tried simply copying the part of the standard library that implements those traits, but
	it relies on too much of the rest of the standard library. I don't want importing HappyLock to
	require recompiling the entire standard library.
</p>

<p>
	The next feasible solution, in my opinion, would be to make a new crate that uses the operating
	system mutexes, and implements <code>lock_api</code> traits on them. I've already started doing this,
	and it seems very possible. But don't expect it to properly support every platform that the standard
	library supports.
</p>

<h3>Compile-Time Duplicate Checks</h3>

<p>
	The only way to make duplicate checks free is to run them at compile-time. I doubt I'd be able to
	implement this using procedural macros (feel free to prove me wrong). But, as
	<a href="https://github.com/pacak">pacak</a> keeps reminding me, it might be possible using const
	functions with a <a href="https://en.wikipedia.org/wiki/Bloom_filter">Bloom filter</a>. I don't know
	if const Rust is robust enough for this to be implemented yet, but I do want to give it a try. It
	wouldn't work for lock collections which require sorting, but it might work for
	<code>RetryingLockCollection</code>.
</p>

<h3>Expanding Cyclic Wait</h3>

<p>
	One other thing I'd like to explore is if there's anything more that can be done with cyclic wait.
	I could imagine <code>LockCollection&lt;Vec&lt;L>> where L: OwnedLockable</code> having some
	<code>lock_next</code> method, or maybe an iterator type that would allow you to only lock the first
	few items as they are needed, rather than locking the entire collection at once. I'm not sure how
	useful this would be though. I'm sure there are other ideas that I'm not thinking of too.
</p>

<h3>A <code>LockCell</code> type</h3>

<p>
	If you've seen the <code>Cell</code> type in Rust, then you know that it's a good zero-cost
	abstraction that provides safe interior mutability. The downsides are that it cannot be shared
	between threads and its limited API. But I think it would be neat to add some of its methods as
	convenience methods for <code>Mutex</code> and <code>RwLock</code>, i.e:
	<code>Mutex::lock_swap</code>. This would not remove the need for a <code>ThreadKey</code>, since
	then you'd be able to call <code>lock_swap</code> on a thread that's already locked the mutex.
</p>

<p>
	However, if we had methods like <code>Mutex::try_swap</code>, then there'd be no need. The mutex
	wouldn't be able to block the current thread, and because the lock is released so quickly, it
	wouldn't block any other threads either. No blocking, means no deadlock. I think the same would be
	true for <code>try_clone</code> and <code>try_take</code>, but they're scarier since the
	<code>Clone</code> and <code>Default</code> traits could try to lock the same mutex, resulting in a
	deadlock. I don't know if it would be possible to access the <code>ThreadKey</code> safely in these
	methods though. Either way, all of these methods would be fallible, so it's not going to be a
	perfect solution.
</p>

<p>
	But we could introduce a <code>LockCell</code> type, which would be the same as <code>Cell</code>,
	but it safely implements <code>Sync</code> by using a mutex or rwlock internally. This would not
	require <code>ThreadKey</code> at all, as long as all of the implemented methods don't lock the
	<code>LockCell</code> a second time, which is probably impossible.
</p>

<p>
	It is a little surprising that a type like this doesn't already exist in the standard library. That
	might be because it would make race conditions very easy to trigger. Imagine checking to see if two
	<code>LockCell</code> values were equal for an if-block, but once you get inside the if-block,
	they're no longer equal. I'll have to think more about this.
</p>

<pre>
let a: LockCell&lt;i32>;
let b: LockCell&lt;i32>;

if a == b {
	// a and b are no longer equal
}
</pre>

<h3>Readonly Lock Collections</h3>

<p>
	I originally had a different idea of what to do with the <code>Sharable</code> trait. We could avoid
	checking for duplicates in a lock collection if we knew that the only thing we would do with them is
	read. At the time, I thought that reading twice on the same thread will never cause a deadlock.
	However, that's not true on every platform, because of contention. Luckily, the <code>lock_api</code>
	developers thought ahead by designing a <code>RawRwLockRecursive</code> trait, which I can extend by
	creating a <code>Recursive</code> subtrait of <code>Lockable</code>.
</p>

<p>
	That's cool, but I don't really want to add three more lock collection types for this feature. So
	maybe this could be a wrapper around the existing lock collection types, i.e.
	<code>Readonly&lt;<wbr/>RetryingLockCollection&lt;<wbr/>L>></code>. I'd need to implement a
	<code>LockCollection</code> trait that would have an associated unsafe constructor for creating the
	collection without checking for duplicates. Then, creating that retrying lock collection would be
	free (but locking it can still sometimes be expensive).
</p>

<p>
	This doesn't only apply to <code>RwLock</code>. If there was a <code>ReentrantMutex</code> type, then
	we could implement <code>Recursive</code> on that, and use that in a readonly lock collection as well.
	For those of you who don't know, a <code>ReentrantMutex</code> can be locked several times on the same
	thread, but not while it's being locked by a different thread. However, the guard can only access the
	data immutably, because multiple mutable references to data on the same thread is still undefined
	behavior. It's mostly intended for wrapping <code>Cell</code> or <code>RefCell</code>. Unfortunately,
	<code>lock_api</code> doesn't have a trait for recursive mutexes. Instead it provides a struct
	wrapper for <code>RawMutex</code>, called <code>RawReentrantMutex</code>. This is despite the fact
	that Unix's pthread API already supports recursive mutexes. I might be able to sway the
	<code>lock_api</code> developers to support this.
</p>

<h3>No Standard Library</h3>

<p>
	Doing this without the standard library would be very interesting. But currently, the
	<code>Lockable</code> interface requires a <code>Vec</code> of pointers, so it might not be feasible.
	The thread keys are stored in a thread local storage too, but I'll probably come up with some
	<code>UnsafeKey</code> system to deal with that eventually, in order to support async runtimes.
</p>

<h3>Other Types from the Standard Library</h3>

<p>
	It would be fun to add <code>OnceLock</code> or <code>LazyLock</code>, but I don't think it's
	necessary. I don't think anybody has ever accidentally deadlocked using those interfaces, if at all.
	In the past I've also considered <code>Condvar</code> and <code>Barrier</code>, but that's trickier.
	They're kind of the opposite of a mutex, so tricks that work for avoiding deadlocks here won't work
	on them. I've also never used either of those types before, so I'm probably not the best person to
	figure that out.
</p>

<h2>Conclusion</h2>

<p>
	This has been a very long nerd snipe. But I think this idea could become incredibly useful, which is
	why I keep pushing for it. It'd be even better if it was a part of the standard libary to begin with,
	but I'm aware that's not possible anymore.
</p>

<p>
	The fact that this is possible makes me appreciate the borrow checker even more. People often think
	of it as a limitation (myself included), but it solves so many problems that I'm surprised more
	languages don't adopt it. Not only does it solve memory corruption, but also resource leaks, and now
	deadlocks. You spend more time specifying your code, but less time debugging it.
</p>

<p>
	There's still work that needs to be done (I haven't even begun async runtime support yet), but I hope
	that as this library becomes more developed, there will be more projects which are immune this error.
</p>

</article>
</body>