Shutdown signal improvements: - Upgrade ShutdownSignal from Relaxed to Release/Acquire ordering. Relaxed was technically sufficient for a single flag but Release/Acquire is the textbook correct pattern and ensures visibility guarantees across threads without relying on x86 TSO. - Add double Ctrl+C support to all three signal handlers (ingest, embed, sync). First Ctrl+C sets cooperative flag with user message; second Ctrl+C force-exits with code 130 (standard SIGINT convention). CLI hardening: - LORE_ROBOT env var now checks for truthy values (!empty, !="0", !="false") instead of mere existence. Setting LORE_ROBOT=0 or LORE_ROBOT=false no longer activates robot mode. - Replace unreachable!() in color mode match with defensive warning and fallback to auto. Clap validates the values but defense in depth prevents panics if the value_parser is ever changed. - Replace unreachable!() in completions shell match with proper error return for unsupported shells. Exit code collision fix: - ConfigNotFound was mapped to exit code 2 (error.rs:56) which collided with handle_clap_error() also using exit code 2 for parse errors. Agents calling lore --robot could not distinguish "bad arguments" from "missing config file." - Restore ConfigNotFound to exit code 20 (its original dedicated code). - Update robot-docs exit code table: code 2 = "Usage error", code 20 = "Config not found". Build script: - Track .git/refs/heads directory for Cargo rebuild triggers. Ensures GIT_HASH env var updates when branch refs change, not just HEAD. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
64 lines
1.4 KiB
Rust
64 lines
1.4 KiB
Rust
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
/// A cooperative cancellation token for graceful shutdown.
|
|
///
|
|
/// Clone-able and cheaply checkable from any thread or async task.
|
|
/// When `cancel()` is called (typically from a Ctrl+C signal handler),
|
|
/// all clones observe the cancellation via `is_cancelled()`.
|
|
#[derive(Clone)]
|
|
pub struct ShutdownSignal {
|
|
cancelled: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl ShutdownSignal {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
cancelled: Arc::new(AtomicBool::new(false)),
|
|
}
|
|
}
|
|
|
|
pub fn cancel(&self) {
|
|
self.cancelled.store(true, Ordering::Release);
|
|
}
|
|
|
|
pub fn is_cancelled(&self) -> bool {
|
|
self.cancelled.load(Ordering::Acquire)
|
|
}
|
|
}
|
|
|
|
impl Default for ShutdownSignal {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn signal_starts_uncancelled() {
|
|
let signal = ShutdownSignal::new();
|
|
assert!(!signal.is_cancelled());
|
|
}
|
|
|
|
#[test]
|
|
fn cancel_sets_flag() {
|
|
let signal = ShutdownSignal::new();
|
|
signal.cancel();
|
|
assert!(signal.is_cancelled());
|
|
}
|
|
|
|
#[test]
|
|
fn clone_propagates_cancellation() {
|
|
let signal = ShutdownSignal::new();
|
|
let clone = signal.clone();
|
|
signal.cancel();
|
|
assert!(
|
|
clone.is_cancelled(),
|
|
"clone should see cancellation from original"
|
|
);
|
|
}
|
|
}
|