use std::path::PathBuf;
use std::sync::{Arc, Mutex, PoisonError};
use tracing::{debug, info, warn};
use super::pid_file::{
PidRecord, StaleReconciliation, lock_pid_mutation, pid_file_error, read_path,
write_record_atomically,
};
use crate::error::ServerError;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RecordUpdate {
Written,
Unclaimed,
NotOurs,
}
#[derive(Debug)]
pub struct PidFileGuard {
path: PathBuf,
record: Arc<Mutex<PidRecord>>,
reconciliation: StaleReconciliation,
holds_claim: bool,
}
impl PidFileGuard {
pub(super) fn claimed(
path: PathBuf,
record: PidRecord,
reconciliation: StaleReconciliation,
) -> Self {
Self {
path,
record: Arc::new(Mutex::new(record)),
reconciliation,
holds_claim: true,
}
}
pub(super) fn unclaimed(
path: PathBuf,
record: PidRecord,
reconciliation: StaleReconciliation,
) -> Self {
Self {
path,
record: Arc::new(Mutex::new(record)),
reconciliation,
holds_claim: false,
}
}
#[must_use]
pub fn reconciliation(&self) -> &StaleReconciliation {
&self.reconciliation
}
pub fn record(&self) -> Result<PidRecord, ServerError> {
self.record
.lock()
.map(|record| record.clone())
.map_err(|_poisoned| {
pid_file_error(
"the in-process pid record lock is poisoned: a previous update \
panicked, so this incarnation's copy of its own record cannot be \
trusted",
)
})
}
#[must_use]
pub fn holds_claim(&self) -> bool {
self.holds_claim
}
pub fn update_own(
&self,
change: impl FnOnce(&mut PidRecord),
) -> Result<RecordUpdate, ServerError> {
update_record(&self.path, &self.record, self.holds_claim, change)
}
#[must_use]
pub fn stage_reporter(&self) -> super::stage::StageReporter {
super::stage::StageReporter::new(
self.path.clone(),
Arc::clone(&self.record),
self.holds_claim,
)
}
}
pub(super) fn update_record(
path: &std::path::Path,
shared: &Mutex<PidRecord>,
holds_claim: bool,
change: impl FnOnce(&mut PidRecord),
) -> Result<RecordUpdate, ServerError> {
if !holds_claim {
debug!(
path = %path.display(),
"this incarnation booted unclaimed; not writing its own pid record"
);
return Ok(RecordUpdate::Unclaimed);
}
let mut ours = shared.lock().map_err(|_poisoned| {
pid_file_error(
"the in-process pid record lock is poisoned: a previous update panicked, \
so this incarnation's copy of its own record cannot be trusted",
)
})?;
let mutation_lock = lock_pid_mutation(path)?;
let current = read_path(path)?;
let outcome = match current {
Some(current) if current.is_same_incarnation(&ours) => {
let mut next = current;
change(&mut next);
write_record_atomically(path, &next)?;
*ours = next;
RecordUpdate::Written
}
Some(_) => {
warn!(
path = %path.display(),
pid = ours.pid,
"the pid file no longer holds this incarnation's record (a successor \
claimed the home); leaving it alone rather than overwriting a live \
server's address"
);
RecordUpdate::NotOurs
}
None => {
warn!(
path = %path.display(),
pid = ours.pid,
"the pid file is GONE although this incarnation holds the claim — it \
was removed out from under the running server; not recreating it \
mid-life, so the disappearance stays visible"
);
RecordUpdate::NotOurs
}
};
drop(mutation_lock);
Ok(outcome)
}
impl Drop for PidFileGuard {
fn drop(&mut self) {
if !self.holds_claim {
return;
}
let ours = self
.record
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone();
let mutation_lock = match lock_pid_mutation(&self.path) {
Ok(lock) => lock,
Err(error) => {
warn!(
path = %self.path.display(),
%error,
"could not take the pid mutation lock on exit; leaving the pid \
file for stale reconciliation"
);
return;
}
};
let current = match std::fs::read_to_string(&self.path) {
Ok(content) => match serde_json::from_str::<PidRecord>(&content) {
Ok(record) => Some(record),
Err(parse_error) => {
warn!(
path = %self.path.display(),
%parse_error,
"the pid file does not parse at exit time; leaving it for \
stale reconciliation rather than deleting a record this \
binary cannot compare"
);
None
}
},
Err(io_error) if io_error.kind() == std::io::ErrorKind::NotFound => {
warn!(
path = %self.path.display(),
"the pid file is GONE at exit time although this incarnation \
held the claim — it was removed out from under the running \
server; nothing to reconcile, recording the disappearance"
);
None
}
Err(io_error) => {
warn!(
path = %self.path.display(),
%io_error,
"could not read the pid file at exit time; leaving it for \
stale reconciliation"
);
None
}
};
match current {
Some(record) if record.is_same_incarnation(&ours) => {
if let Err(io_error) = std::fs::remove_file(&self.path) {
warn!(
path = %self.path.display(),
%io_error,
"could not remove the pid file on exit; `aion server stop` \
and the next boot both reconcile it as stale"
);
} else {
info!(path = %self.path.display(), "pid file removed on exit");
}
}
Some(_) => {
info!(
path = %self.path.display(),
"pid file now holds a different incarnation's record; leaving it"
);
}
None => {}
}
drop(mutation_lock);
}
}
#[cfg(test)]
#[path = "guard_tests.rs"]
mod tests;