use crate::service_index_attempts::{Placing, wait_and_place};
use crate::wait_ceilings::LOCK_CEILING;
use crate::service_protocol::FailureCode;
use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::service_index_prestage::{Prestaged, take};
pub use crate::service_index_found::{WaitNotes, describe_lock};
pub use crate::service_index_hold::IndexLock;
pub use crate::service_index_marker::{LOCK_MARKER, Marked, marker_text, parse_marker};
pub use crate::service_index_ownership::{Ownership, inspect_ownership, unsettled_locks};
#[cfg(target_os = "linux")]
pub use crate::service_index_watch::{events_end_watch, events_name};
pub use crate::service_index_placement::{Platform, SYSTEM, placement_failure_text};
pub use crate::service_index_staging::{STAGED_INFIX, staged_path_for};
pub use crate::service_index_watch::Watch;
use crate::service_index_placement::{log_placement_failure, place_marked, reclaim};
pub use crate::service_index_reclaim::{Reclaimed, foreign_words, remove_stale, stale_lock_words, unjudged_words};
use crate::service_index_staging::{refresh_age, remove_dead_staging, stage_marker_synced};
pub const NOTICE_EVERY: Duration = Duration::from_secs(10);
pub const NOTICE_VARIABLE: &str = "CLI_GIT_WAIT_NOTICE_SECONDS";
pub(crate) fn notice_every() -> Duration {
return notice_interval(std::env::var(NOTICE_VARIABLE).ok().as_deref());
}
pub(crate) fn notice_interval(setting: Option<&str>) -> Duration {
return setting.and_then(|text| { return text.trim().parse::<u64>().ok(); }).filter(|seconds| { return *seconds > 0; }).map_or(NOTICE_EVERY, Duration::from_secs);
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Announce {
Waiting(String),
Holder(String),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LockFailure {
pub code: FailureCode,
}
pub fn try_acquire(index: &Path, seq: u64) -> Option<IndexLock> {
return try_acquire_on(&SYSTEM, index, seq);
}
pub fn try_acquire_on(platform: &Platform, index: &Path, seq: u64) -> Option<IndexLock> {
let lock: PathBuf = lock_path_of(index);
let marker: String = match marker_text(seq, platform.identity) {
Ok(text) => text,
Err(error) => {
crate::service_error!("cannot take the index lock {}: this process has no birth identity to name in the lock ({error})", lock.display());
return None;
}
};
let (staged, held) = match stage_marker_synced(platform.sync, &lock, seq, marker.as_str()) {
Ok(made) => made,
Err(error) => {
log_placement_failure(&lock, &error);
return None;
}
};
refresh_age(&held);
let placed: std::io::Result<()> = match place_marked(platform, &staged, &lock) {
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => match reclaim(platform, &lock) {
Reclaimed::Removed | Reclaimed::Changed => place_marked(platform, &staged, &lock),
Reclaimed::Busy | Reclaimed::Kept | Reclaimed::Stuck | Reclaimed::Unguarded(_) | Reclaimed::Foreign { .. } | Reclaimed::Unjudged { .. } => Err(error),
},
other => other,
};
let _removed = std::fs::remove_file(&staged);
if let Err(error) = &placed {
log_placement_failure(&lock, error);
return None;
}
remove_dead_staging(&lock);
return Some(IndexLock::placed(lock, index, marker, held, (platform.inspect, platform.release)));
}
pub fn lock_path_of(index: &Path) -> PathBuf {
let mut name = index.as_os_str().to_os_string();
name.push(".lock");
return PathBuf::from(name);
}
pub fn acquire(index: &Path, seq: u64, announce: &dyn Fn(Announce)) -> Result<IndexLock, LockFailure> {
return acquire_noting_holder(index, seq, announce, &mut None);
}
pub fn acquire_noting_holder(index: &Path, seq: u64, announce: &dyn Fn(Announce), holder: &mut Option<String>) -> Result<IndexLock, LockFailure> {
let mut notes: WaitNotes = WaitNotes::default();
let taken: Result<IndexLock, LockFailure> = acquire_placing(&SYSTEM, index, seq, LOCK_CEILING, announce, &mut notes, None);
holder.clone_from(¬es.holder);
return taken;
}
pub fn acquire_noting_wait(index: &Path, seq: u64, announce: &dyn Fn(Announce), notes: &mut WaitNotes) -> Result<IndexLock, LockFailure> {
return acquire_noting_wait_within(index, seq, LOCK_CEILING, announce, notes);
}
pub fn acquire_noting_wait_within(index: &Path, seq: u64, ceiling: Duration, announce: &dyn Fn(Announce), notes: &mut WaitNotes) -> Result<IndexLock, LockFailure> {
let preparing: std::time::Instant = std::time::Instant::now();
let prepared: Option<Prestaged> = take(index, seq);
notes.staging = preparing.elapsed();
return acquire_placing(&SYSTEM, index, seq, ceiling, announce, notes, prepared);
}
pub fn acquire_on(platform: &Platform, index: &Path, seq: u64, announce: &dyn Fn(Announce)) -> Result<IndexLock, LockFailure> {
return acquire_placing(platform, index, seq, LOCK_CEILING, announce, &mut WaitNotes::default(), None);
}
pub fn acquire_within(platform: &Platform, index: &Path, seq: u64, ceiling: Duration, announce: &dyn Fn(Announce)) -> Result<IndexLock, LockFailure> {
return acquire_placing(platform, index, seq, ceiling, announce, &mut WaitNotes::default(), None);
}
fn acquire_placing(
platform: &Platform,
index: &Path,
seq: u64,
ceiling: Duration,
announce: &dyn Fn(Announce),
notes: &mut WaitNotes,
prestaged: Option<Prestaged>,
) -> Result<IndexLock, LockFailure> {
let lock: PathBuf = lock_path_of(index);
let directory: &Path = lock.parent().unwrap_or(Path::new("."));
let lock_name: std::ffi::OsString = lock.file_name().map(std::ffi::OsStr::to_os_string).unwrap_or_default();
let mut watch: Option<Watch> = (platform.watch)(directory);
let (marker, staged, held) = match prestaged {
Some(made) => {
notes.stage_kind = "pre";
(made.marker, made.staged, made.held)
}
None => {
let marker: String = match marker_text(seq, platform.identity) {
Ok(text) => text,
Err(error) => {
crate::service_error!("cannot take the index lock {}: this process has no birth identity to name in the lock ({error})", lock.display());
return Err(LockFailure { code: FailureCode::Unavailable });
}
};
let staging_began: std::time::Instant = std::time::Instant::now();
let staged_result = stage_marker_synced(platform.sync, &lock, seq, marker.as_str());
notes.staging += staging_began.elapsed();
notes.stage_kind = "cold";
let (staged, held) = staged_result.map_err(|error| {
log_placement_failure(&lock, &error);
return LockFailure { code: crate::service_land_follow::storage_failure(&error) };
})?;
(marker, staged, held)
}
};
let placing_began: std::time::Instant = std::time::Instant::now();
let outcome: Result<(), LockFailure> = wait_and_place(
platform,
&Placing { staged: &staged, held: &held, lock: &lock, lock_name: lock_name.as_os_str(), index, ceiling },
&mut watch,
announce,
notes,
);
notes.placing = placing_began.elapsed();
let _removed = std::fs::remove_file(&staged);
outcome?;
remove_dead_staging(&lock);
return Ok(IndexLock::placed(lock, index, marker, held, (platform.inspect, platform.release)));
}
#[cfg(test)]
#[path = "service_index_reclaim_tests.rs"]
mod reclaim_tests;
#[cfg(test)]
#[path = "service_index_claim_tests.rs"]
mod claim_tests;
#[cfg(test)]
#[path = "service_index_machine_tests.rs"]
mod machine_tests;
#[cfg(test)]
#[path = "service_index_lock_tests.rs"]
mod tests;