Initial commit
This commit is contained in:
commit
ea3b41eb1d
8 changed files with 429 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal 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
9
Cargo.toml
Normal 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
42
README.md
Normal 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
28
examples/channel.rs
Normal 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
93
src/lib.rs
Normal 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
167
src/timed.rs
Normal 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
82
src/timer.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue