use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
static CRITICAL_SECTION_DEPTH: AtomicUsize = AtomicUsize::new(0);
#[inline]
pub fn is_interrupted() -> bool {
INTERRUPTED.load(Ordering::Relaxed)
}
#[inline]
pub fn is_interrupted_outside_critical() -> bool {
if CRITICAL_SECTION_DEPTH.load(Ordering::Relaxed) > 0 {
false
} else {
INTERRUPTED.load(Ordering::Relaxed)
}
}
#[allow(dead_code)]
pub fn reset_interrupted() {
INTERRUPTED.store(false, Ordering::Relaxed);
}
#[inline]
pub fn enter_critical_section() {
CRITICAL_SECTION_DEPTH.fetch_add(1, Ordering::SeqCst);
}
#[inline]
pub fn exit_critical_section() {
CRITICAL_SECTION_DEPTH.fetch_sub(1, Ordering::SeqCst);
}
pub struct CriticalSectionGuard;
impl Default for CriticalSectionGuard {
fn default() -> Self {
Self::new()
}
}
impl CriticalSectionGuard {
pub fn new() -> Self {
enter_critical_section();
CriticalSectionGuard
}
}
impl Drop for CriticalSectionGuard {
fn drop(&mut self) {
exit_critical_section();
}
}
pub fn setup_signal_handler() -> Result<(), ctrlc::Error> {
ctrlc::set_handler(move || {
if !INTERRUPTED.swap(true, Ordering::SeqCst) {
if CRITICAL_SECTION_DEPTH.load(Ordering::Relaxed) > 0 {
eprintln!("\n⚠️ Interrupt received — finishing the current write, then stopping…");
} else {
eprintln!(
"\n⚠️ Interrupt received — stopping at the next checkpoint. \
Finished batches are kept; rerun to resume."
);
}
}
})
}
#[derive(Debug, Clone)]
pub struct InterruptedError;
impl std::fmt::Display for InterruptedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Operation interrupted by user")
}
}
impl std::error::Error for InterruptedError {}
#[inline]
pub fn check_interrupted() -> Result<(), InterruptedError> {
if is_interrupted() {
Err(InterruptedError)
} else {
Ok(())
}
}