use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(test)]
use std::sync::Mutex;
use std::path::Path;
pub(crate) mod checkpoint;
#[path = "runtime.rs"]
mod handling;
pub use checkpoint::InterruptContext;
pub use handling::{clear_interrupt_context, get_interrupt_context, set_interrupt_context};
static USER_INTERRUPT_REQUESTED: AtomicBool = AtomicBool::new(false);
static USER_INTERRUPTED_OCCURRED: AtomicBool = AtomicBool::new(false);
pub(crate) static EVENT_LOOP_ACTIVE: AtomicBool = AtomicBool::new(false);
pub(crate) static EVENT_LOOP_ACTIVE_SIGINT_COUNT: AtomicUsize = AtomicUsize::new(0);
static EXIT_130_AFTER_RUN: AtomicBool = AtomicBool::new(false);
pub fn request_exit_130_after_run() {
EXIT_130_AFTER_RUN.store(true, Ordering::SeqCst);
}
pub fn take_exit_130_after_run() -> bool {
EXIT_130_AFTER_RUN.swap(false, Ordering::SeqCst)
}
#[cfg(unix)]
fn restore_prompt_md_writable_via_std_fs() {
if handling::restore_prompt_md_writable(std::path::Path::new("PROMPT.md")) {
return;
}
let Ok(repo_root) = crate::git_helpers::get_repo_root() else {
return;
};
let _ = handling::restore_prompt_md_writable_in_repo(&repo_root);
}
fn remove_repo_root_ralph_dir_via_std_fs() {
let repo_root = handling::get_interrupt_context()
.map(|ctx| ctx.workspace.root().to_path_buf())
.or_else(|| crate::git_helpers::get_repo_root().ok());
if let Some(repo_root) = repo_root {
handling::remove_ralph_dir(&repo_root);
}
}
#[cfg(not(unix))]
fn restore_prompt_md_writable_via_std_fs() {}
pub struct EventLoopActiveGuard;
impl Drop for EventLoopActiveGuard {
fn drop(&mut self) {
EVENT_LOOP_ACTIVE.store(false, Ordering::SeqCst);
EVENT_LOOP_ACTIVE_SIGINT_COUNT.store(0, Ordering::SeqCst);
}
}
pub fn event_loop_active_guard() -> EventLoopActiveGuard {
EVENT_LOOP_ACTIVE_SIGINT_COUNT.store(0, Ordering::SeqCst);
EVENT_LOOP_ACTIVE.store(true, Ordering::SeqCst);
EventLoopActiveGuard
}
fn is_event_loop_active() -> bool {
EVENT_LOOP_ACTIVE.load(Ordering::SeqCst)
}
pub(crate) fn register_sigint_during_active_event_loop() -> bool {
let count = EVENT_LOOP_ACTIVE_SIGINT_COUNT.fetch_add(1, Ordering::SeqCst) + 1;
count >= 2
}
pub fn request_user_interrupt() {
USER_INTERRUPT_REQUESTED.store(true, Ordering::SeqCst);
USER_INTERRUPTED_OCCURRED.store(true, Ordering::SeqCst);
}
pub fn user_interrupted_occurred() -> bool {
USER_INTERRUPTED_OCCURRED.load(Ordering::SeqCst)
}
pub fn is_user_interrupt_requested() -> bool {
USER_INTERRUPT_REQUESTED.load(Ordering::SeqCst)
}
pub fn take_user_interrupt_request() -> bool {
USER_INTERRUPT_REQUESTED.swap(false, Ordering::SeqCst)
}
#[cfg(test)]
pub fn reset_user_interrupted_occurred() {
USER_INTERRUPTED_OCCURRED.store(false, Ordering::SeqCst);
}
#[cfg(test)]
static TEST_INTERRUPT_LOCK: Mutex<()> = Mutex::new(());
#[cfg(test)]
pub(crate) fn interrupt_test_lock() -> std::sync::MutexGuard<'static, ()> {
TEST_INTERRUPT_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[expect(clippy::print_stderr, reason = "critical interrupt handling messages")]
pub fn setup_interrupt_handler() {
let install = ctrlc::set_handler(|| {
request_user_interrupt();
if is_event_loop_active() {
if register_sigint_during_active_event_loop() {
eprintln!("\nSecond interrupt received; forcing immediate exit.");
restore_prompt_md_writable_via_std_fs();
eprintln!("Cleaning up...");
crate::git_helpers::cleanup_agent_phase_silent();
remove_repo_root_ralph_dir_via_std_fs();
handling::exit_sigint();
}
eprintln!(
"\nInterrupt received; requesting graceful shutdown (waiting for checkpoint)..."
);
return;
}
eprintln!("\nInterrupt received; saving checkpoint...");
let context = handling::get_interrupt_context();
if let Some(ref context) = context {
if let Err(e) = checkpoint::save_interrupt_checkpoint(context) {
eprintln!("Warning: Failed to save checkpoint: {e}");
} else {
eprintln!("Checkpoint saved. Resume with: ralph --resume");
}
}
restore_prompt_md_writable_via_std_fs();
if let Some(ref context) = context {
let _ = context.workspace.set_writable(Path::new("PROMPT.md"));
}
eprintln!("Cleaning up...");
crate::git_helpers::cleanup_agent_phase_silent();
remove_repo_root_ralph_dir_via_std_fs();
handling::exit_sigint();
});
if let Err(e) = install {
eprintln!("Warning: failed to install Ctrl+C handler: {e}");
}
}
#[cfg(test)]
mod io_tests {
include!("io_tests.rs");
}