use super::index_lock_evidence::{Verdict, classify_index_lock, gather_index_lock_evidence, proven_holder_line, proven_holder_progress_line};
use super::service_index_lock::{Announce, LockFailure, notice_every};
use super::service_index_marker::is_reclaimable;
use super::service_index_placement::{Platform, log_placement_failure, place_marked, reclaim};
use super::service_index_staging::refresh_age;
use super::service_index_watch::Watch;
use super::service_protocol::FailureCode;
use std::path::Path;
use std::time::{Duration, Instant};
fn holder_words(pid: i64, command: Option<&str>) -> String {
return match command {
Some(name) => format!("Process {pid} ({name})"),
None => format!("Process {pid}"),
};
}
fn holder_notice(index: &Path, waited: Duration, limit: Duration, first: bool) -> (Option<String>, Option<String>) {
let Ok(Some(evidence)) = gather_index_lock_evidence(index) else { return (None, None) };
let Verdict::ProvenAlive { pid, by_descriptor, command } = classify_index_lock(&evidence) else {
let line: String = format!(
"cli-git: waiting for {}, whose owner cannot be proven alive, after {} seconds; giving up after {} seconds.\n",
evidence.lock_path.display(),
waited.as_secs(),
limit.as_secs()
);
return (Some(line), None);
};
let line: String = if first {
proven_holder_line(pid, by_descriptor, command.as_deref(), evidence.lock_path.as_path(), limit.as_secs())
} else {
proven_holder_progress_line(pid, by_descriptor, command.as_deref(), evidence.lock_path.as_path(), waited.as_secs(), limit.as_secs())
};
return (Some(line), Some(holder_words(pid, command.as_deref())));
}
#[derive(Clone, Copy)]
pub(super) struct Placing<'a> {
pub(super) staged: &'a Path,
pub(super) held: &'a std::fs::File,
pub(super) lock: &'a Path,
pub(super) lock_name: &'a std::ffi::OsStr,
pub(super) index: &'a Path,
pub(super) limit: Duration,
}
pub(super) fn wait_and_place(platform: &Platform, placing: &Placing, watch: &mut Option<Watch>, announce: &dyn Fn(Announce), holder: &mut Option<String>) -> Result<(), LockFailure> {
let Placing { staged, held, lock, lock_name, index, limit } = *placing;
let directory: &Path = lock.parent().unwrap_or(Path::new("."));
let began: Instant = Instant::now();
let mut first: bool = true;
let mut last_notice: Instant = began;
loop {
refresh_age(held);
match place_marked(platform, staged, lock) {
Ok(()) => return Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {
if is_reclaimable(lock) {
if !reclaim(platform, lock) {
return Err(LockFailure { code: FailureCode::Unavailable });
}
continue;
}
}
Err(error) => {
log_placement_failure(lock, &error);
return Err(LockFailure { code: super::service_land_follow::storage_failure(&error) });
}
}
let waited: Duration = began.elapsed();
let exhausted: bool = waited >= limit || watch.is_none();
let due: bool = first || last_notice.elapsed() >= notice_every();
let (line, who) = if due || exhausted { holder_notice(index, waited, limit, first) } else { (None, None) };
if who.is_some() {
holder.clone_from(&who);
}
if due {
if let Some(text) = line {
announce(Announce::Waiting(text));
}
first = false;
last_notice = Instant::now();
}
if exhausted {
announce(Announce::Holder(who.unwrap_or_else(|| { return String::from("Another program"); })));
return Err(LockFailure { code: FailureCode::IndexInUse });
}
if let Some(active) = watch.as_ref()
&& !active.wait(notice_every().saturating_sub(last_notice.elapsed()).max(Duration::from_millis(1)).min(limit.saturating_sub(waited)), lock_name)
{
drop(watch.take());
*watch = (platform.watch)(directory);
}
}
}