use crate::child_class::ChildClass;
use crate::wait_progress::{Cadence, Ticker};
use crate::bounded_push::Patience;
use crate::push_progress::Clock;
use std::io::Write;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
use crate::stopped_git_locks::LockLedger;
use crate::bounded_push::STOP_GRACE;
use crate::process_tree::{TreeMember, descendants_of, kill_members, member_of, still_running, terminate_members};
use std::time::{Duration, Instant};
pub(crate) const SHORTEST_SLEEP: Duration = Duration::from_millis(1);
pub struct WatchTerms {
pub limit: Duration,
pub cadence: Cadence,
pub notices: Box<dyn Write + Send>,
}
impl WatchTerms {
pub fn for_class(class: ChildClass) -> Self {
let interval: Cadence = Cadence::from_environment();
let limit: Duration = class.limit();
return Self {
limit,
cadence: Cadence { grace: grace_for(class, interval.interval, limit), interval: interval.interval },
notices: Box::new(std::io::stderr()),
};
}
pub fn from_environment() -> Self {
return Self::for_class(ChildClass::Object);
}
}
pub fn grace_for(class: ChildClass, interval: Duration, limit: Duration) -> Duration {
return class.notice_grace().min(interval).min(limit / 2);
}
pub(crate) enum Mark {
Begin(Instant),
End,
}
pub(crate) const STOP_POLL: Duration = Duration::from_millis(10);
pub(crate) struct Marks {
pub(crate) since: Option<Instant>,
pub(crate) ticker: Ticker,
pub(crate) printed: u32,
pub(crate) clock: Option<Clock>,
pub(crate) pid: u32,
pub(crate) window: Duration,
pub(crate) longest: Arc<AtomicU64>,
}
impl Marks {
pub(crate) fn apply(&mut self, mark: Mark, cadence: Cadence) {
match mark {
Mark::Begin(at) => {
self.since = Some(at);
self.ticker = Ticker::new(cadence);
self.printed = 0;
self.clock = Some(Clock::start(self.pid, Patience::Progress { stall: self.window }).without_wall_fallback());
}
Mark::End => {
self.keep_gap();
self.since = None;
self.clock = None;
}
}
}
pub(crate) fn keep_gap(&self) {
if let Some(clock) = self.clock.as_ref()
&& clock.measures()
{
let micros: u64 = u64::try_from(clock.longest_quiet().as_micros()).unwrap_or(u64::MAX);
self.longest.fetch_max(micros, Ordering::AcqRel);
}
}
}
#[cfg(unix)]
pub(crate) fn signal_root(root: &Option<TreeMember>, identified: bool, raw: i32, signal: libc::c_int) {
if root.as_ref().map_or(!identified, still_running) {
unsafe {
libc::kill(raw, signal);
}
}
}
#[cfg(unix)]
pub(crate) fn stop_process(pid: u32, locks: &LockLedger, incoming: &Receiver<Mark>) {
let Ok(raw) = i32::try_from(pid) else {
return;
};
let below: Vec<TreeMember> = descendants_of(raw);
let root: Option<TreeMember> = member_of(raw);
let mut tree: Vec<TreeMember> = root.iter().cloned().collect();
tree.extend(below.iter().cloned());
let identified: bool = cfg!(target_os = "linux");
signal_root(&root, identified, raw, libc::SIGTERM);
terminate_members(below.as_slice());
let asked: Instant = Instant::now();
while asked.elapsed() < STOP_GRACE && tree.iter().any(still_running) {
std::thread::sleep(STOP_POLL);
}
signal_root(&root, identified, raw, libc::SIGKILL);
kill_members(below.as_slice());
while tree.iter().any(still_running) {
match incoming.recv_timeout(STOP_POLL) {
Err(RecvTimeoutError::Disconnected) => break,
Ok(_) | Err(RecvTimeoutError::Timeout) => {}
}
}
locks.sweep(tree.as_slice());
}
#[cfg(not(unix))]
pub(crate) fn stop_process(_pid: u32, _locks: &LockLedger, _incoming: &Receiver<Mark>) {}
pub(crate) struct WatchFlags {
pub(crate) expired: Arc<AtomicBool>,
pub(crate) stoppable: Arc<AtomicBool>,
pub(crate) longest: Arc<AtomicU64>,
pub(crate) ceiling: Arc<AtomicU64>,
pub(crate) ceiling_hit: Arc<AtomicBool>,
}