use degenbot_cli_core::CancelHandle;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
pub enum Action {
Cancel,
Abort,
}
pub const fn action(already_cancelled: bool) -> Action {
if already_cancelled {
Action::Abort
} else {
Action::Cancel
}
}
#[derive(Debug)]
#[must_use = "the guard must outlive the command run"]
pub struct Guard {
_runtime: tokio::runtime::Runtime,
}
#[cfg(unix)]
#[must_use]
pub fn install(cancel: CancelHandle) -> Option<Guard> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.ok()?;
runtime.spawn(listen(cancel));
Some(Guard { _runtime: runtime })
}
#[cfg(not(unix))]
#[must_use]
pub fn install(_cancel: CancelHandle) -> Option<Guard> {
degenbot_core::op_warn!(
domain = pump,
"SIGINT handling is Unix-only; cooperative cancel is unavailable"
);
None
}
#[cfg(unix)]
async fn listen(cancel: CancelHandle) {
let Ok(mut interrupts) =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
else {
degenbot_core::op_warn!(
domain = pump,
"could not install the SIGINT handler; cooperative cancel is unavailable"
);
return;
};
loop {
if interrupts.recv().await.is_none() {
return;
}
match action(cancel.is_cancelled()) {
Action::Cancel => {
cancel.cancel();
degenbot_core::op_warn!(
domain = pump,
"interrupt received: finishing the in-flight chunk, then stopping (press Ctrl+C again to abort)"
);
}
Action::Abort => {
degenbot_core::op_warn!(domain = pump, "second interrupt received: aborting");
std::process::abort();
}
}
}
}
#[cfg(test)]
mod tests {
use super::{action, Action};
#[test]
fn first_interrupt_cancels_and_second_aborts() {
assert_eq!(action(false), Action::Cancel);
assert_eq!(action(true), Action::Abort);
}
}