signal-hook
Safe, structured Unix signal handling for Rust applications
Repository Health
Technical Analysis
signal-hook is a Rust library that solves one of the trickiest problems in systems programming: handling Unix signals safely and correctly. Signals are process-wide, can fire on any thread, and the POSIX standard forbids most familiar operations — including locking and memory allocation — inside a signal handler. signal-hook works around these constraints by providing a global registry that lets independent parts of a program subscribe to and unsubscribe from the same signal without stepping on each other’s handlers.
Rather than forcing every consumer to write raw signal-handling code, it ships ready-made patterns for the most common cases: setting an atomic flag when a signal arrives, writing a byte to a self-pipe to wake up a poll loop, or iterating over signals as they arrive on a dedicated thread. Adapter crates extend the same primitives to Tokio, async-std, and mio for applications built on those runtimes.
What You Get
- A global signal registry (
low_level) for registering and unregistering raw signal actions across independent parts of an application. - The
flagmodule for setting atomic bools/flags on signal arrival — ideal for polling loops that check for shutdown requests. - The
iteratormodule’sSignals/SignalsInfotypes for blocking iteration over incoming signals on a dedicated thread. - The
low_level::pipemodule for waking uppoll/select-based event loops via a self-pipe when a signal fires. - Companion crates (
signal-hook-tokio,signal-hook-async-std,signal-hook-mio) that adapt the same registry to async runtimes.
Common Use Cases
- Graceful shutdown: catching SIGTERM/SIGINT to finish in-flight work before a daemon or server exits.
- Config reload: watching for SIGHUP to re-read configuration files without restarting a long-running process.
- Double-Ctrl-C handling: forcing immediate exit if a second termination signal arrives while a graceful shutdown is already underway.
- Integrating signal delivery into async runtimes (Tokio, async-std, mio) via the companion adapter crates.
Under The Hood
Architecture The crate splits into a layered architecture around a shared backend. At the bottom sits the signal-hook-registry crate (signal-hook-registry/src/lib.rs, half_lock.rs, vec_map.rs), which owns the single OS-level sigaction() registration per signal and multiplexes an arbitrary number of callbacks onto it via a half_lock structure, guaranteeing the callbacks it invokes are async-signal-safe. signal-hook itself (src/lib.rs) re-exports this registry through low_level (src/low_level/mod.rs, pipe.rs, channel.rs, siginfo.rs, signal_details.rs) and layers three progressively higher-level, safe APIs on top: flag (src/flag.rs) for atomic-flag polling, low_level::pipe for waking poll/select loops via a self-pipe, and iterator (src/iterator/mod.rs, backend.rs, exfiltrator/*.rs) for blocking, thread-based signal consumption with pluggable Exfiltrator implementations controlling what data (bare signal number vs. extended siginfo) comes out of each delivery. The companion crates signal-hook-tokio, signal-hook-async-std, and signal-hook-mio (separate workspace members per Cargo.toml) each wrap the same iterator backend to bridge signal delivery into their respective async runtimes’ polling models, so no signal-handling logic is duplicated per runtime.
Tech Stack signal-hook is pure Rust 2018 (rust-version = 1.66 per Cargo.toml), with a minimal dependency footprint: libc (^0.2) for raw signal syscalls and its own signal-hook-registry (^1.4, path-linked within the workspace) as the only mandatory dependency. Feature flags in Cargo.toml (channel, iterator — both on by default, extended-siginfo, extended-siginfo-raw) let consumers opt out of the iterator or channel modules to shrink compiled surface area; extended-siginfo-raw pulls in an optional cc build dependency and a build.rs to compile a small C shim for extracting extended siginfo_t fields not exposed safely by libc. The repo is a Cargo workspace (Cargo.toml [workspace] members) housing five crates — the core, the registry backend, and three async-runtime adapters — versioned and released together, with dev-dependencies limited to an in-tree serial_test crate for serializing signal-touching tests.
Code Quality Testing is concentrated in three integration-test files (tests/default.rs, tests/iterator.rs at 272 lines, tests/shutdown.rs) rather than unit tests inline with the source, reflecting that signal tests must run serially and often send real signals to the test process — the serial_test dev-dependency exists specifically to prevent these tests from racing each other. The crate is warn(missing_docs)-enforced (top of src/lib.rs) and every public module carries substantial doc comments with runnable, doctested examples (e.g. the flag and iterator module docs both compile as doctests under test(attr(deny(warnings)))), a strong documentation discipline signal for a systems crate. Error handling is idiomatic std::io::Error throughout the public API rather than a custom error type, keeping the surface small; naming is consistent and the low_level:: / flag:: / iterator:: module prefixes make call sites self-documenting about which safety/abstraction tier is in use.
API Design The public API is deliberately tiered by ergonomics and risk: low_level::register offers raw, unsafe-adjacent primitives for library authors building their own abstractions, while flag::register_usize, low_level::pipe, and iterator::Signals give application authors safe, one-call entry points for the three dominant signal-handling patterns (poll a flag, wake an event loop, iterate on a thread) without needing to understand sigaction semantics. The type alias Signals = SignalsInfo<SignalOnly> keeps the common case simple while the generic SignalsInfo<Exfiltrator> parameter lets advanced users opt into origin or raw siginfo data without a separate API. Getting started requires minimal boilerplate — the README’s example is a five-line loop around Signals::new — and the crate’s chaining behavior (calling through to a pre-existing handler instead of clobbering it) is a thoughtful default that most competing signal crates don’t handle automatically.
Used by 3 apps in this directory
CubeSandbox
Developer Tools · Security · AI Agents
Instant, concurrent, hardware-isolated MicroVM sandboxes for AI agents — E2B-API compatible, sub-60ms cold starts, and a built-in zero-trust egress proxy, all self-hostable at scale.
PostgresML
Databases · AI Development
Run ML training and LLM inference natively inside PostgreSQL with GPU acceleration — no data movement required.
Trieve
AI Development · Search · Developer Tools
All-in-one self-hostable platform for hybrid search, RAG, recommendations, and analytics built on Rust and Qdrant.