signal-msg
Handle UNIX process signals with a shared channel
About
This project makes UNIX signal handling simple: just listen for signals on a
channel. Instead of putting logic inside signal-handler closures (which are
restricted to async-signal-safe operations and cannot easily share state),
signal-msg delivers signals as messages to one or more [Receiver]s.
Internally the library uses the self-pipe trick — the OS-level handler writes a single byte into a pipe (the only async-signal-safe work it does), and a background thread reads from the pipe and fans the signal out to all subscribers. A more feature-rich solution is available via the signal-hook library.
Similar functionality to signal-msg is provided by the
signal-notify and
chan-signal libraries (note, though,
that the latter is deprecated and recommends exploring both signal-hook and crossbeam-channel).
Learning Experiment
This project was born in early 2020, when Rust was a new and exciting language to explore. Coming
from backgrounds in Erlang, Clojure, and recently Go — languages where message-passing and channels are
first-class citizens — the idea of wrapping UNIX signal handling in a familiar channel-based API
felt like a natural first real-world project. It was a chance to poke at std::sync::mpsc, write
some traits, publish a crate, and generally get a feel for how Rust thinks about ownership,
concurrency, and interfacing with the OS.
Fast-forward to 2026: coming back to the project made it immediately clear that the library isn't really necessary. The ecosystem has matured beautifully — see the About section above for better-maintained, more feature-complete alternatives worth exploring. But rather than shelving it, it seemed like a much better idea to double down on the learning angle: bring the code up to current Rust standards, apply everything learned since 2020, and document the journey version by version.
Sometimes the best way to appreciate how far you've come is to revisit where you started :-)
| Version | Goal | Fixes / Changes | Knowledge Gained |
|---|---|---|---|
| v0.1.0 | Wrap signal handling in a channel-based API using simple-signal |
Initial implementation with mpsc channel and SignalReceiver trait |
std::sync::mpsc, traits, crate publishing basics |
| v0.2.0 | Symmetry and ergonomics | Added SignalSender trait, renamed methods, added convenience constructor |
Trait design, API ergonomics, Rust naming conventions |
| v0.3.0 | Fix rough edges found by a fresh audit | Proper Display + Error impls, replaced panics with Results, cleaner public API surface |
Idiomatic error handling, std::error::Error trait chain |
| v0.4.0 | Correctness — the original design had a subtle but serious bug | Dropped simple-signal, rewrote with libc; self-pipe trick for async-signal-safe delivery; Arc<Mutex<Vec<Sender>>> fan-out for multiple subscribers |
POSIX async-signal-safety, self-pipe pattern, FFI with libc, broadcast channel design |
| v0.5.0 | Safety and resilience | // SAFETY: docs on all unsafe blocks, OsError(std::io::Error) with source(), mutex poison recovery, named background thread, fcntl error checking, try_listen() |
Unsafe code documentation, error chaining, Mutex poison semantics, non-blocking channel patterns |
| v0.6.0 | Compile-time API safety and final polish | Merged prepare() into new() (typestate pattern), all unsafe in private functions, #![cfg(unix)] platform gate, EINTR retry in dispatch loop, Signal::from_raw in impl Signal, corrected MSRV to 1.63 |
Typestate pattern, POSIX EINTR semantics, platform gating, MSRV semantics |
| v0.7.0 | Signal semantics and iterator ergonomics | Added SIGUSR1, SIGUSR2, SIGWINCH, SIGCONT, SIGURG; Signal::is_terminating() to classify exit vs. informational signals; Iterator impl on Receiver; demo updated to loop using for sig in receiver |
Signal categorization, Iterator for channel receivers, loop-as-expression pattern |
Usage
use Signals;
Example
Run the bundled demo in one terminal:
then, in a second terminal, walk through all non-terminating signals before finishing with SIGTERM:
PID=
for; do
done
Credits
The project logo is derived from the "signpost" icon in the motorway icon set by Freepik.
License
Copyright © 2020-2026, Oxur Group
MIT License
