summaryrefslogtreecommitdiff
path: root/src/lock.rs
blob: fb74f25cdec2af8abe02468b376f44cfbd5ad79a (plain)
use std::sync::atomic::{AtomicBool, Ordering};

/// A dumb lock that's just a wrapper for an [`AtomicBool`].
#[derive(Debug, Default)]
pub struct Lock {
	is_locked: AtomicBool,
}

#[derive(Debug)]
pub struct Key<'a> {
	lock: &'a Lock,
}

impl<'a> Key<'a> {
	fn new(lock: &'a Lock) -> Self {
		Self { lock }
	}
}

impl<'a> Drop for Key<'a> {
	fn drop(&mut self) {
		// safety: this key will soon be destroyed
		unsafe { self.lock.force_unlock() }
	}
}

impl Lock {
	/// Create a new unlocked `Lock`.
	pub const fn new() -> Self {
		Self {
			is_locked: AtomicBool::new(false),
		}
	}

	/// Attempt to lock the `Lock`.
	///
	/// If the lock is already locked, then this'll return false. If it is
	/// unlocked, it'll return true. If the lock is currently unlocked, then it
	/// will be locked after this function is called.
	///
	/// This is not a fair lock. It is not recommended to call this function
	/// repeatedly in a loop.
	pub fn try_lock(&self) -> Option<Key> {
		(!self.is_locked.fetch_or(true, Ordering::Acquire)).then_some(Key::new(self))
	}

	/// Unlock the lock, without a key.
	///
	/// # Safety
	///
	/// This should only be called if the key to the lock has been "lost". That
	/// means the program no longer has a reference to the key, but it has not
	/// been dropped.
	unsafe fn force_unlock(&self) {
		self.is_locked.store(false, Ordering::Release)
	}

	pub fn unlock(key: Key) {
		drop(key)
	}
}