# Signal Handling and Shutdown Coordination
CoreShift-Core provides a centralized mechanism for managing process shutdown via standard Unix signals (`SIGINT`, `SIGTERM`), plus a full `SignalRuntime` for blocking, waiting, and dispatching signals.
## Design Goal
Core does not impose a shutdown policy. Instead, it provides a thread-safe way for a signal handler to notify the application's main loop that a shutdown has been requested, and low-level primitives for masking/waiting/signalfd.
## Shutdown Primitives
### `install_shutdown_flag`
`install_shutdown_flag(flag: &'static AtomicBool)` installs global signal handlers for `SIGINT` and `SIGTERM`. When either signal is received, the handler sets the provided `AtomicBool` to `true`.
This is intended for the application's primary entry point.
### `install_shutdown_flag_guard`
`install_shutdown_flag_guard(flag: &'static AtomicBool)` is the scoped version of the above. It returns a guard that, when dropped, restores the previous signal handlers and the previous shutdown flag pointer.
This is primarily used in tests or transient worker threads to ensure isolation.
### `shutdown_requested`
`shutdown_requested(flag: &AtomicBool)` is a convenience helper to check the current state of the shutdown flag using the correct atomic ordering (`Ordering::Acquire`).
## Typical Usage Pattern
```rust
use coreshift_core::signal;
use std::sync::atomic::AtomicBool;
static SHUTDOWN: AtomicBool = AtomicBool::new(false);
fn main() -> Result<(), coreshift_core::CoreError> {
// Install the global handler
signal::install_shutdown_flag(&SHUTDOWN)?;
while !signal::shutdown_requested(&SHUTDOWN) {
// Perform work...
// If a signal arrives, the loop will exit gracefully.
}
Ok(())
}
```
## `SignalRuntime`
`SignalRuntime` is the lower-level surface for explicit signal control, aimed at reactor-integrated daemons:
- **Masking**: `empty_set` / `set_with(signals)` build a `SignalSet`; `block_current_thread` / `restore_current_thread` / `set_current_thread_mask` manage the calling thread's signal mask (`pthread_sigmask`).
- **Waiting**: `wait(signals)` blocks the current thread until a signal in the set arrives and returns it; `signalfd_new(signals)` creates a `signalfd` for reactor-compatible reception — the fd is non-blocking + `CLOEXEC`, and the **caller** must block the signals on its own thread before reading from it.
- **Thread targeting**: `interrupt_thread(thread, signal)` targets a specific thread (`pthread_kill`; `ESRCH` surfaces).
- **Handlers**: `register_handler(sig, handler)` installs an arbitrary process-wide handler and **returns the previous `libc::sigaction`**; `reset_default(sig)` restores `SIG_DFL`; `reset_ignored_to_default()` scans signals 1..=64 (skipping `SIGKILL`/`SIGSTOP`) and resets any `SIG_IGN`-ed handler to default — best-effort, all errors tolerated.
- **Convenience**: `kill(pid, sig)`; `blocked(signals)` **blocks immediately** (`SIG_BLOCK`) and returns a `BlockedSignals` guard that restores the previous mask on drop; `unblock_all()`.
## Lower-level items
- Constants: `SIGINT`, `SIGTERM`, `SIGPIPE`, `SIGKILL`, `SIGUSR1`, `SIGUSR2`,
`SIGCHLD`, `SIGHUP` (8 exported `i32` consts).
- Type aliases: `SignalSet = libc::sigset_t`, `ThreadId = libc::pthread_t`,
`SignalfdSiginfo = libc::signalfd_siginfo`.
- `signal_ignore(sig)` (`unsafe`) — install `SIG_IGN` (BSD `libc::signal`), no
result. Used by spawn children to neutralize inherited ignores; note
`reset_ignored_to_default` is the reverse operation.
## Safety Invariants
- **Signal Safety**: The internal shutdown handler is async-signal-safe. It only performs a `store` operation on an atomic pointer.
- **Global State**: Core uses a single `static AtomicPtr<AtomicBool>` to track the active flag (`SHUTDOWN_FLAG_PTR`, signal.rs:46). Only one flag can be active at a time per process.
- **Race closure (CORE-M11)**: `SIGINT`/`SIGTERM` are blocked for the entire install (and the entire guard drop), so no signal can be delivered between handler swap and flag-pointer swap.
- **`unblock_all` (CORE-M12)**: sets the whole mask to empty — only safe in a single-threaded fork child.
- **Signalfd restore**: `Reactor::drop` restores the signalfd mask only if it runs on the same thread that set it up (`pthread_equal`). `BlockedSignals::drop` restores the mask unconditionally on whatever thread drops it — do not drop it from a different thread than the one that created it. Also note `blocked()` swallows `pthread_sigmask` failure and would then restore an empty mask; avoid relying on it after an error.
- **Footguns**: `set_with` reports `EINVAL` on any `sigaddset` failure; `register_handler` installs with `sa_flags = 0` (no `SA_RESTART`).