pub mod cleanup;
mod error;
pub mod finally;
pub mod guard;
pub mod resource;
#[cfg(test)]
mod tests;
use std::time::Duration;
use either::Either::{Left, Right};
use rand::Rng;
use crate::lock;
pub use error::CoordinateError;
use self::finally::with_finally;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum State {
Modified,
Unmodified,
}
pub trait Subject {
type Error: std::error::Error + Send + Sync;
type Options<'a>: Default;
fn start(&self, options: Self::Options<'_>) -> Result<State, Self::Error>;
fn stop(&self) -> Result<State, Self::Error>;
fn destroy(&self) -> Result<State, Self::Error>;
fn exists(&self) -> Result<bool, Self::Error>;
fn running(&self) -> Result<bool, Self::Error>;
}
pub fn run_and_stop<S, F, T>(
subject: &S,
options: S::Options<'_>,
lock: lock::UnlockedFile,
action: F,
) -> Result<T, CoordinateError<S::Error>>
where
S: std::panic::RefUnwindSafe + Subject,
F: std::panic::UnwindSafe + FnOnce() -> T,
{
let lock = startup(lock, subject, options)?;
with_finally(
|| shutdown::<S, _, _>(lock, || subject.stop()),
|| -> Result<T, CoordinateError<S::Error>> { Ok(action()) },
)
}
pub fn run_and_stop_if_exists<S, F, T>(
subject: &S,
options: S::Options<'_>,
lock: lock::UnlockedFile,
action: F,
) -> Result<T, CoordinateError<S::Error>>
where
S: std::panic::RefUnwindSafe + Subject,
F: std::panic::UnwindSafe + FnOnce() -> T,
{
let lock = startup_if_exists(lock, subject, options)?;
with_finally(
|| shutdown::<S, _, _>(lock, || subject.stop()),
|| -> Result<T, CoordinateError<S::Error>> { Ok(action()) },
)
}
pub fn run_and_destroy<S, F, T>(
subject: &S,
options: S::Options<'_>,
lock: lock::UnlockedFile,
action: F,
) -> Result<T, CoordinateError<S::Error>>
where
S: std::panic::RefUnwindSafe + Subject,
F: std::panic::UnwindSafe + FnOnce() -> T,
{
let lock = startup(lock, subject, options)?;
with_finally(
|| shutdown::<S, _, _>(lock, || subject.destroy()),
|| -> Result<T, CoordinateError<S::Error>> { Ok(action()) },
)
}
fn startup<S: Subject>(
mut lock: lock::UnlockedFile,
control: &S,
options: S::Options<'_>,
) -> Result<lock::LockedFileShared, CoordinateError<S::Error>> {
loop {
lock = match lock.try_lock_exclusive() {
Ok(Left(lock)) => {
let lock = lock.lock_shared()?;
if control.running().map_err(CoordinateError::ControlError)? {
return Ok(lock);
}
let lock = lock.unlock()?;
let delay = rand::rng().next_u32();
let delay = 200 + (delay % 800);
let delay = Duration::from_millis(u64::from(delay));
std::thread::sleep(delay);
lock
}
Ok(Right(lock)) => {
control
.start(options)
.map_err(CoordinateError::ControlError)?;
return Ok(lock.lock_shared()?);
}
Err(err) => return Err(err.into()),
};
}
}
fn startup_if_exists<S: Subject>(
mut lock: lock::UnlockedFile,
subject: &S,
options: S::Options<'_>,
) -> Result<lock::LockedFileShared, CoordinateError<S::Error>> {
loop {
lock = match lock.try_lock_exclusive() {
Ok(Left(lock)) => {
let lock = lock.lock_shared()?;
if subject.running().map_err(CoordinateError::ControlError)? {
return Ok(lock);
}
let lock = lock.unlock()?;
let delay = rand::rng().next_u32();
let delay = 200 + (delay % 800);
let delay = Duration::from_millis(u64::from(delay));
std::thread::sleep(delay);
lock
}
Ok(Right(lock)) => {
if subject.exists().map_err(CoordinateError::ControlError)? {
subject
.start(options)
.map_err(CoordinateError::ControlError)?;
} else {
return Err(CoordinateError::DoesNotExist);
}
return Ok(lock.lock_shared()?);
}
Err(err) => return Err(err.into()),
};
}
}
fn shutdown<S, F, T>(
lock: lock::LockedFileShared,
action: F,
) -> Result<Option<T>, CoordinateError<S::Error>>
where
S: Subject,
F: FnOnce() -> Result<T, S::Error>,
{
match lock.try_lock_exclusive() {
Ok(Left(lock)) => {
lock.unlock()?;
Ok(None)
}
Ok(Right(lock)) => {
match action() {
Ok(result) => {
lock.unlock()?;
Ok(Some(result))
}
Err(err) => Err(CoordinateError::ControlError(err)),
}
}
Err(err) => Err(err.into()),
}
}