I am toying around with Rust and various UNIX libraries. A use-case that I have right now is that I want to react to POSIX signals. To keep things reasonable I want to create an abstraction over the signal handling so that the rest of my program doesn’t have to worry about them as much.
Let’s call the abstraction SignalHandler
:
struct SignalHandler { pub signals: Arc<Vec<libc::c_int>>, }
I would like this signals
vector to be filled with all the signals that are received. My real state is more complicated, but let’s use this vector as an example.
I want the API to behave like this:
// ← No signals are being captured let Some(h) = SignalHandler::try_create(); // ← Signals are added to h.signals // Only one signal handler can be active at a time per process assert_eq!(None, SignalHandler::try_create()); // ← Signals are added to h.signals drop(h); // ← No signals are being captured
The problem is that registering a signal handler (e.g. using the nix
crate) requires a pointer to a C function:
use nix::sys::signal; let action = signal::SigAction::new(handle_signal, signal::SockFlag::empty(), signal::SigSet::empty()); signal::sigaction(signal::SIGINT, &action);
I can’t pass the signals
vector to the handle_signal
function, since it needs to have the C ABI and thus can’t be a closure. I would like to give out a Weak<_>
pointer to that function somehow. This probably means using global state.
So the question is: what data structure should I use for global state that can either be “unset” (i.e. no signals
vector) or atomically “set” to some mutable state that I initialize in try_create
?
Advertisement
Answer
For this type of global state, I would recommend using the lazy_static crate. You can use a macro to define a lazily-evaluated, mutable global reference. You may be able to get a way with a global Option<T>
variable with that.
That is one problem with this situation though. A big issue you will run into is that it is hard to do what you want only inside of a signal handler. Since a signal handler must be re-entrant, any type of locks are out as well as any memory allocation (unless the memory allocator used is also re-entrant). That means an Arc<Mutex<Vec<T>>>
type or something similar will not work. You potentially already know and are dealing with that in some way though.
Depending on your needs, I might point you towards the chan_signal crate, which is an abstraction over signals which uses a thread and the sigwait
syscall to receive signals.
Hope that helps, another interesting resource to look at would be the signalfd function which creates a file descriptor to enqueue signals on. The nix
crate has a binding to that as well.