diff --git a/Cargo.lock b/Cargo.lock index e64a73f..a6f4435 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "microlock" -version = "0.1.0" +version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 70fa8e1..2a66944 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "microlock" description = "A crate for waiting: Small locks and other timing things!" license = "MIT" repository = "https://git.tudbut.de/tudbut/microlock" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] diff --git a/src/lib.rs b/src/lib.rs index 89eff8f..c5374c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,41 +1,9 @@ mod timed; pub mod timer; +mod untimed; pub use timed::*; -use timer::{Timed, Timer, TimerDuration}; - -use std::{ - sync::{Condvar, Mutex}, - time::Duration, -}; - -/// An untimed lock. This can be locked and unlocked, but it will never unlock -/// on its own (see [`TimedLock`] for an expiring lock). If a thread calls -/// wait_here on a locked lock, it will wait until the lock is unlocked by -/// another thread. -pub struct UntimedLock { - locked: Mutex, - condvar: Condvar, -} - -impl UntimedLock { - /// Creates a new untimed lock that is either unlocked or locked. - pub const fn new(locked: bool) -> Self { - Self { - locked: Mutex::new(locked), - condvar: Condvar::new(), - } - } - - /// Creates a new untimed lock that is unlocked. - pub const fn unlocked() -> Self { - Self::new(false) - } - - /// Creates a new untimed lock that is locked. - pub const fn locked() -> Self { - Self::new(true) - } -} +use timer::TimerDuration; +pub use untimed::*; pub trait Lock { /// Returns if the lock is (still) locked. @@ -43,6 +11,11 @@ pub trait Lock { /// Locks the lock indefinitely. In case of a timed one, the previous /// target will be replaced without releasing the waiting threads. fn lock(&self); + /// Locks the lock indefinitely like [`lock`], UNLESS unlock was called + /// more than once. If someone tried to unlock the lock while it was + /// already unlocked, this will NOT lock it. Returns false and resets + /// the double-unlock check if the locking failed. + fn try_lock(&self) -> bool; /// Unlocks the lock and releases all waiting threads. fn unlock(&self); /// Makes the current thread wait on this lock until it is @@ -53,41 +26,3 @@ pub trait Lock { fn wait_here_for(&self, timeout: TimerDuration); fn wait_here_for_ms(&self, timeout_ms: u64); } - -impl Lock for UntimedLock { - fn is_locked(&self) -> bool { - *self.locked.lock().unwrap() - } - - fn lock(&self) { - *self.locked.lock().unwrap() = true; - } - - fn unlock(&self) { - *self.locked.lock().unwrap() = false; - self.condvar.notify_all(); - } - - fn wait_here(&self) { - let mut locked = self.locked.lock().unwrap(); - while *locked { - locked = self.condvar.wait(locked).unwrap(); - } - } - - fn wait_here_for(&self, timeout: TimerDuration) { - let mut locked = self.locked.lock().unwrap(); - let timer = Timer::new(timeout); - while *locked && !timer.has_elapsed() { - locked = self - .condvar - .wait_timeout(locked, timer.time_left().to_real()) - .unwrap() - .0; - } - } - - fn wait_here_for_ms(&self, timeout_ms: u64) { - self.wait_here_for(TimerDuration::Real(Duration::from_millis(timeout_ms))); - } -} diff --git a/src/timed.rs b/src/timed.rs index af1bab8..afee051 100644 --- a/src/timed.rs +++ b/src/timed.rs @@ -89,8 +89,7 @@ impl TimedLock { let mut data = self.data.lock().unwrap(); data.time = Some(Timer::new(duration)); data.remain_locked = true; - self.inner.lock(); - drop(data); + self.inner.lock() } pub fn lock_for_ms(&self, duration_ms: u64) { @@ -121,16 +120,21 @@ impl Lock for TimedLock { let mut data = self.data.lock().unwrap(); data.time = None; data.remain_locked = true; - self.inner.lock(); - drop(data); + self.inner.lock() + } + + fn try_lock(&self) -> bool { + let mut data = self.data.lock().unwrap(); + data.time = None; + data.remain_locked = true; + self.inner.try_lock() } fn unlock(&self) { let mut data = self.data.lock().unwrap(); data.time = None; data.remain_locked = false; - self.inner.unlock(); - drop(data); + self.inner.unlock() } fn wait_here(&self) { diff --git a/src/untimed.rs b/src/untimed.rs new file mode 100644 index 0000000..7decc11 --- /dev/null +++ b/src/untimed.rs @@ -0,0 +1,96 @@ +use std::{ + sync::{Condvar, Mutex}, + time::Duration, +}; + +use crate::{ + timer::{Timed, Timer, TimerDuration}, + Lock, +}; + +/// An untimed lock. This can be locked and unlocked, but it will never unlock +/// on its own (see [`TimedLock`] for an expiring lock). If a thread calls +/// wait_here on a locked lock, it will wait until the lock is unlocked by +/// another thread. +pub struct UntimedLock { + locked: Mutex, + double_unlock: Mutex, + condvar: Condvar, +} + +impl UntimedLock { + /// Creates a new untimed lock that is either unlocked or locked. + pub const fn new(locked: bool) -> Self { + Self { + locked: Mutex::new(locked), + double_unlock: Mutex::new(false), + condvar: Condvar::new(), + } + } + + /// Creates a new untimed lock that is unlocked. + pub const fn unlocked() -> Self { + Self::new(false) + } + + /// Creates a new untimed lock that is locked. + pub const fn locked() -> Self { + Self::new(true) + } +} + +impl Lock for UntimedLock { + fn is_locked(&self) -> bool { + *self.locked.lock().unwrap() + } + + fn lock(&self) { + *self.locked.lock().unwrap() = true; + *self.double_unlock.lock().unwrap() = false; + } + + fn try_lock(&self) -> bool { + let mut double_unlock = self.double_unlock.lock().unwrap(); + if !*double_unlock { + *self.locked.lock().unwrap() = true; + return true; + } + *double_unlock = false; + drop(double_unlock); + false + } + + fn unlock(&self) { + let mut locked = self.locked.lock().unwrap(); + let mut double_unlock = self.double_unlock.lock().unwrap(); + if !*locked { + *double_unlock = true; + return; + } + *locked = false; + self.condvar.notify_all(); + } + + fn wait_here(&self) { + let mut locked = self.locked.lock().unwrap(); + while *locked { + locked = self.condvar.wait(locked).unwrap(); + } + } + + fn wait_here_for(&self, timeout: TimerDuration) { + let mut locked = self.locked.lock().unwrap(); + let timer = Timer::new(timeout); + while *locked && !timer.has_elapsed() { + locked = self + .condvar + .wait_timeout(locked, timer.time_left().to_real()) + .unwrap() + .0; + } + } + + fn wait_here_for_ms(&self, timeout_ms: u64) { + self.wait_here_for(TimerDuration::Real(Duration::from_millis(timeout_ms))); + } +}