use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
use super::ReferenceError;
use super::store::Lock;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum LockWait {
#[default]
Immediate,
For(Duration),
UntilCancelled,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub struct FilesTransactionOptions {
pub reference_lock_wait: LockWait,
pub packed_refs_lock_wait: LockWait,
}
impl FilesTransactionOptions {
#[must_use]
pub fn with_reference_lock_wait(mut self, reference_lock_wait: LockWait) -> Self {
self.reference_lock_wait = reference_lock_wait;
self
}
#[must_use]
pub fn with_packed_refs_lock_wait(mut self, packed_refs_lock_wait: LockWait) -> Self {
self.packed_refs_lock_wait = packed_refs_lock_wait;
self
}
}
impl FilesTransactionOptions {
pub const GIT_DEFAULT: Self = Self {
reference_lock_wait: LockWait::For(Duration::from_millis(100)),
packed_refs_lock_wait: LockWait::For(Duration::from_secs(1)),
};
}
pub(super) fn check_cancelled(cancel: &AtomicBool) -> Result<(), ReferenceError> {
if cancel.load(Ordering::Relaxed) {
Err(ReferenceError::Cancelled)
} else {
Ok(())
}
}
impl Lock {
pub(super) fn acquire_wait(
destination: PathBuf,
wait: LockWait,
cancel: &AtomicBool,
) -> Result<Self, ReferenceError> {
let generation = crate::locks::wait_generation();
let started = Instant::now();
let mut delay = Duration::from_millis(1);
loop {
check_cancelled(cancel)?;
if crate::locks::wait_generation() != generation {
return Err(ReferenceError::Cancelled);
}
let error = match Self::acquire(destination.clone()) {
Ok(lock) => {
if crate::locks::wait_generation() != generation {
return Err(ReferenceError::Cancelled);
}
return Ok(lock);
}
Err(error @ ReferenceError::Locked(_)) => error,
Err(error) => return Err(error),
};
let sleep = match wait {
LockWait::Immediate => return Err(error),
LockWait::For(budget) => {
let remaining = budget.saturating_sub(started.elapsed());
if remaining.is_zero() {
return Err(error);
}
delay.min(remaining)
}
LockWait::UntilCancelled => delay,
};
std::thread::sleep(sleep);
check_cancelled(cancel)?;
if crate::locks::wait_generation() != generation {
return Err(ReferenceError::Cancelled);
}
if let LockWait::For(budget) = wait
&& started.elapsed() >= budget
{
return Err(error);
}
delay = (delay * 2).min(Duration::from_millis(20));
}
}
}