Initial commit

This commit is contained in:
Daniella / Tove 2024-07-13 23:58:49 +00:00
commit ea3b41eb1d
8 changed files with 429 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "microlock"
version = "0.1.0"

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
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"
edition = "2021"
[dependencies]

42
README.md Normal file
View file

@ -0,0 +1,42 @@
# microlock
Microlock is a smallish crate for waiting. It contains an UntimedLock suitable
for complex synchronization, and a TimedLock that can also function as such,
but additionally provides timer functionalities.
## Use-case:
The untimed lock can be used similarly to a channel, with added flexibility.
In this example, it simply replaces a channel, but more complex use-cases are
significantly less trivial with channels.
```rs
use std::{
collections::VecDeque,
sync::{Arc, Mutex},
thread,
};
use microlock::{Lock, TimedLock, UntimedLock};
fn main() {
static LOCK: UntimedLock = UntimedLock::locked();
let queue = Arc::new(Mutex::new(VecDeque::new()));
let queue2 = queue.clone();
thread::spawn(move || loop {
LOCK.wait_here();
println!("{}", queue2.lock().unwrap().pop_front().unwrap());
LOCK.lock();
});
let timer = TimedLock::unlocked();
for i in 0..5 {
timer.lock_for_ms(100);
println!("Sending {i}...");
queue.lock().unwrap().push_back(format!("Hello! {i}"));
LOCK.unlock();
println!("Sent!");
timer.wait_here();
}
}
```

28
examples/channel.rs Normal file
View file

@ -0,0 +1,28 @@
use std::{
collections::VecDeque,
sync::{Arc, Mutex},
thread,
};
use microlock::{Lock, TimedLock, UntimedLock};
fn main() {
static LOCK: UntimedLock = UntimedLock::locked();
let queue = Arc::new(Mutex::new(VecDeque::new()));
let queue2 = queue.clone();
thread::spawn(move || loop {
LOCK.wait_here();
println!("{}", queue2.lock().unwrap().pop_front().unwrap());
LOCK.lock();
});
let timer = TimedLock::unlocked();
for i in 0..5 {
timer.lock_for_ms(100);
println!("Sending {i}...");
queue.lock().unwrap().push_back(format!("Hello! {i}"));
LOCK.unlock();
println!("Sent!");
timer.wait_here();
}
}

93
src/lib.rs Normal file
View file

@ -0,0 +1,93 @@
mod timed;
pub mod timer;
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<bool>,
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)
}
}
pub trait Lock {
/// Returns if the lock is (still) locked.
fn is_locked(&self) -> bool;
/// Locks the lock indefinitely. In case of a timed one, the previous
/// target will be replaced without releasing the waiting threads.
fn lock(&self);
/// Unlocks the lock and releases all waiting threads.
fn unlock(&self);
/// Makes the current thread wait on this lock until it is
/// unlocked (or expires).
fn wait_here(&self);
/// Makes the current thread wait on this lock until it is unlocked (or
/// expires) or the timeout expires.
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)));
}
}

167
src/timed.rs Normal file
View file

@ -0,0 +1,167 @@
use std::{sync::Mutex, time::Duration};
use crate::{
timer::{Timed, Timer, TimerDuration},
Lock, UntimedLock,
};
struct TimedLockData {
/// The timer governing when the lock should unlock. This is None if it is
/// in untimed mode or if it is not locked.
time: Option<Timer>,
/// When calling lock(t) on an already locked lock, it will have to unlock
/// and relock the inner UntimedLock. This flag prevents exiting in such a
/// situation, so wait_here* is never exited prematurely.
remain_locked: bool,
}
/// An untimed lock. This can be locked and unlocked, and it will unlock on
/// its own after a timeout, if specified (see [`UntimedLock`] for a
/// non-expiring lock). If a thread calls wait_here on a locked lock, it will
/// wait until the lock is unlocked by another thread, or the lock expires.
pub struct TimedLock {
/// The inner lock, which is used as the implementation for actual locking
/// calls and the `locked` state.
inner: UntimedLock,
/// The data specifying how the lock is currently meant to act. (Most
/// importantly, it contains the Timer)
data: Mutex<TimedLockData>,
}
impl TimedLock {
/// Creates a new timed lock that is either unlocked or locked.
pub const fn new(locked: bool) -> Self {
Self {
inner: UntimedLock::new(locked),
data: Mutex::new(TimedLockData {
time: None,
remain_locked: false,
}),
}
}
/// Creates a new timed lock that is unlocked.
pub const fn unlocked() -> Self {
Self::new(false)
}
/// Creates a new timed lock that is locked.
pub const fn locked() -> Self {
Self::new(true)
}
/// Returns the inner timer. This is None if the lock is in untimed mode
/// or if it is currently unlocked. The timer may already have elapsed
/// if the lock wasn't unlocked explicitly and nobody was waiting on it
/// to trigger an update.
pub fn get_timer(&self) -> Option<Timer> {
self.data.lock().unwrap().time
}
}
impl Timed for TimedLock {
fn has_elapsed(&self) -> bool {
!self.is_locked()
}
/// Returns the current time left until unlocking based on the knowledge
/// the lock has. This may be incorrect if the lock is relocked later on.
fn time_left(&self) -> TimerDuration {
if !self.is_locked() {
return TimerDuration::Elapsed;
}
self.data
.lock()
.unwrap()
.time
.map_or(TimerDuration::Infinite, |x| x.time_left())
}
}
impl TimedLock {
/// Locks the lock for some duration. If the lock is already locked, the
/// old target will be replaced with the new one. This will not wake up
/// any waiting threads, even if relocking is necessary.
pub fn lock_for(&self, duration: TimerDuration) {
if self.is_locked() {
self.inner.unlock();
}
let mut data = self.data.lock().unwrap();
data.time = Some(Timer::new(duration));
data.remain_locked = true;
self.inner.lock();
drop(data);
}
pub fn lock_for_ms(&self, duration_ms: u64) {
self.lock_for(TimerDuration::Real(Duration::from_millis(duration_ms)))
}
}
impl Lock for TimedLock {
/// Checks if the lock should still be locked based on the time, then
/// returns that. This updating is NOT necessary to release waiting
/// threads.
fn is_locked(&self) -> bool {
let data = self.data.lock().unwrap();
if !self.inner.is_locked() {
return false;
}
if let Some(time) = data.time {
if time.has_elapsed() {
drop(data);
self.unlock();
return false;
}
}
true
}
fn lock(&self) {
let mut data = self.data.lock().unwrap();
data.time = None;
data.remain_locked = true;
self.inner.lock();
drop(data);
}
fn unlock(&self) {
let mut data = self.data.lock().unwrap();
data.time = None;
data.remain_locked = false;
self.inner.unlock();
drop(data);
}
fn wait_here(&self) {
loop {
if let Some(time) = self.get_timer() {
self.inner.wait_here_for(time.time_left());
} else {
self.inner.wait_here();
}
if !self.is_locked() || !self.data.lock().unwrap().remain_locked {
break;
}
}
}
fn wait_here_for(&self, timeout: TimerDuration) {
let timer = Timer::new(timeout);
loop {
if let Some(time) = self.get_timer() {
self.inner
.wait_here_for(time.time_left().min(timer.time_left()));
} else {
self.inner.wait_here_for(timer.time_left());
}
if !self.is_locked() || !self.data.lock().unwrap().remain_locked {
break;
}
}
}
fn wait_here_for_ms(&self, timeout_ms: u64) {
self.wait_here_for(Duration::from_millis(timeout_ms).into());
}
}

82
src/timer.rs Normal file
View file

@ -0,0 +1,82 @@
use std::{
cmp::Ordering,
time::{Duration, Instant},
};
pub const INFINITE_DURATION: Duration = Duration::new(u64::MAX, 1_000_000_000 - 1);
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TimerDuration {
Elapsed,
Real(Duration),
Infinite,
}
impl TimerDuration {
pub fn from_difference(a: TimerDuration, b: Duration) -> TimerDuration {
let a = a.to_real();
if a < b {
TimerDuration::Elapsed
} else {
TimerDuration::Real(a - b)
}
}
pub fn to_real(&self) -> Duration {
match *self {
Self::Real(d) if d > Duration::ZERO => d,
Self::Infinite => INFINITE_DURATION,
_ => Duration::new(0, 0),
}
}
}
impl From<Duration> for TimerDuration {
fn from(value: Duration) -> Self {
if value == Duration::ZERO {
return Self::Elapsed;
}
Self::Real(value)
}
}
impl PartialOrd for TimerDuration {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TimerDuration {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(TimerDuration::Elapsed, TimerDuration::Elapsed) => Ordering::Equal,
(TimerDuration::Elapsed, _) => Ordering::Less,
(TimerDuration::Real(_), TimerDuration::Elapsed) => Ordering::Greater,
(TimerDuration::Real(a), TimerDuration::Real(b)) => a.cmp(b),
(TimerDuration::Real(_), TimerDuration::Infinite) => Ordering::Less,
(TimerDuration::Infinite, TimerDuration::Infinite) => Ordering::Equal,
(TimerDuration::Infinite, _) => Ordering::Greater,
}
}
}
#[derive(Clone, Copy)]
pub struct Timer(Instant, TimerDuration);
impl Timer {
pub fn new(d: TimerDuration) -> Self {
Self(Instant::now(), d)
}
pub fn time_elapsed(&self) -> Duration {
self.0.elapsed()
}
}
pub trait Timed {
fn has_elapsed(&self) -> bool;
fn time_left(&self) -> TimerDuration;
}
impl Timed for Timer {
fn has_elapsed(&self) -> bool {
self.0.elapsed() >= self.1.to_real()
}
fn time_left(&self) -> TimerDuration {
TimerDuration::from_difference(self.1, self.0.elapsed())
}
}