Skip to content
Advertisement

Safe global state for signal handling

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.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement