use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
#[cfg(test)]
mod tests;
static STOP: OnceLock<Arc<AtomicBool>> = OnceLock::new();
#[must_use]
pub fn stop_flag() -> Arc<AtomicBool> {
STOP.get_or_init(|| {
let stop = Arc::new(AtomicBool::new(false));
let flag = Arc::clone(&stop);
let installed = ctrlc::set_handler(move || {
if flag.swap(true, Ordering::SeqCst) {
eprintln!("\nSecond interrupt — aborting immediately.");
std::process::exit(130); }
eprintln!(
"\nInterrupt received — finishing the work in flight, then stopping and writing \
what completed (Ctrl+C again to abort now)."
);
});
if installed.is_err() {
eprintln!(
"warning: could not install the Ctrl+C handler; interrupts will not be graceful"
);
}
stop
})
.clone()
}
#[must_use]
pub fn setup_stop_handler() -> Arc<AtomicBool> {
stop_flag()
}
pub fn par_replicates<T, F>(n: usize, what: &str, f: F) -> anyhow::Result<Vec<T>>
where
T: Send,
F: Fn(usize) -> anyhow::Result<T> + Sync + Send,
{
use rayon::prelude::*;
let stop = stop_flag();
let done: Vec<T> = (0..n)
.into_par_iter()
.filter(|_| !stop.load(Ordering::Relaxed))
.map(f)
.collect::<anyhow::Result<Vec<_>>>()?;
anyhow::ensure!(
!done.is_empty(),
"{what} was interrupted before a single replicate completed"
);
if done.len() < n {
interrupted(what, done.len(), n);
}
Ok(done)
}
#[must_use]
pub fn stopped() -> bool {
stop_flag().load(Ordering::Relaxed)
}
pub fn par_reduce_replicates<A, F, C>(
n: usize,
what: &str,
f: F,
combine: C,
) -> anyhow::Result<Option<(usize, A)>>
where
A: Send,
F: Fn(usize) -> anyhow::Result<A> + Sync + Send,
C: Fn(A, A) -> A + Sync + Send,
{
reduce_in(&stop_flag(), n, what, f, combine)
}
fn reduce_in<A, F, C>(
stop: &AtomicBool,
n: usize,
what: &str,
f: F,
combine: C,
) -> anyhow::Result<Option<(usize, A)>>
where
A: Send,
F: Fn(usize) -> anyhow::Result<A> + Sync + Send,
C: Fn(A, A) -> A + Sync + Send,
{
use rayon::prelude::*;
let done = AtomicUsize::new(0);
let acc = (0..n)
.into_par_iter()
.filter(|_| !stop.load(Ordering::Relaxed))
.map(|i| {
let a = f(i)?;
done.fetch_add(1, Ordering::Relaxed);
anyhow::Ok(a)
})
.try_reduce_with(|a, b| Ok(combine(a, b)))
.transpose()?;
let done = done.load(Ordering::Relaxed);
if done < n {
interrupted(what, done, n);
}
Ok(acc.map(|a| (done, a)))
}
fn interrupted(what: &str, done: usize, n: usize) {
log::warn!(
"{what} interrupted: {done} of {n} replicates completed; using those (estimates now \
resolve to ~1/{done})."
);
}