I am using the following code to add signal handling to my C++ classes:
namespace {
std::atomic<bool> signal_flag(false);
}
void terminate_or_interrupt_handler(int signal) {
switch (signal) {
case SIGTERM:
WARN("SIGTERM received");
signal_flag.store(true);
break;
case SIGINT:
WARN("SIGINT received");
signal_flag.store(true);
break;
default:
throw (std::runtime_error("Unhandled signal received"));
}
}
signal(SIGTERM, &terminate_or_interrupt_handler);
This code works, but it requires the signal handler function to be define in the same scope as the signal flag variable. I decided to modify the code and pass the signal_flag
by reference to the function and use std::bind
to "specialize" the handler to my class.
void terminate_or_interrupt_handler(std::atomic<bool>& signal_flag, int signal) {
switch (signal) {
case SIGTERM:
WARN("SIGTERM received");
signal_flag.store(true);
break;
case SIGINT:
WARN("SIGINT received");
signal_flag.store(true);
break;
default:
throw (std::runtime_error("Unhandled signal received"));
}
}
auto my_handler = std::bind(terminate_or_interrupt_handler, std::ref(my_class_signal_flag), std::placeholders::_1);
signal(SIGTERM, &my_handler);
However, I get this compile error:
error: cannot convert ‘std::_Bind<void (*(std::reference_wrapper<std::atomic<bool> >, std::_Placeholder<1>))(std::atomic<bool>&, int)>*’ to ‘__sighandler_t’ {aka ‘void (*)(int)’}
Is there a way to use a bound function in conjunction with the signal
function in C++?
std::bind. std::bind is a Standard Function Objects that acts as a Functional Adaptor i.e. it takes a function as input and returns a new function Object as an output with with one or more of the arguments of passed function bound or rearranged.
std::bind allows you to create a std::function object that acts as a wrapper for the target function (or Callable object). std::bind also allows you to keep specific arguments at fixed values while leaving other arguments variable.
The signal() system call just installs the handler: "signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a programmer-defined function (a "signal handler")." There you go.
std::bind. Returns a function object based on fn , but with its arguments bound to args . Each argument may either be bound to a value or be a placeholder: - If bound to a value, calling the returned function object will always use that value as argument.
The result of std::bind
is an unspecified function object whose type cannot be converted into void (*)(int)
. Try encapsulating it:
void handler_foo(int signal)
{
return terminate_or_interrupt_handler(signal_flag, signal);
}
Or, if C++11 is available, a lambda might be better:
signal(SIGTERM, [](int signal) { return terminate_or_interrupt_handler(signal_flag, signal); });
Note that since signal_flag
is a global variable (namespace-scope variable), no capture is required. A non-capturing lambda can be implicitly converted into the corresponding function pointer type.
If your software runs under Linux and your process does a poll()
or a select()
it may be a lot cleaner to use signalfd()
.
I've implemented such in my eventdispatcher, the ed::signal class¹. Here is a copy of the constructor:
snap_communicator::snap_signal::snap_signal(int posix_signal)
: f_signal(posix_signal)
//, f_socket(-1) -- auto-init
//, f_signal_info() -- auto-init
//, f_unblock(false) -- auto-init
{
int const r(sigismember(&g_signal_handlers, f_signal));
if(r != 0)
{
if(r == 1)
{
// this could be fixed, but probably not worth the trouble...
throw snap_communicator_initialization_error("the same signal cannot be created more than once in your entire process.");
}
// f_signal is not considered valid by this OS
throw snap_communicator_initialization_error("posix_signal (f_signal) is not a valid/recognized signal number.");
}
// create a mask for that signal
//
sigset_t set;
sigemptyset(&set);
sigaddset(&set, f_signal); // ignore error, we already know f_signal is valid
// first we block the signal
//
if(sigprocmask(SIG_BLOCK, &set, nullptr) != 0)
{
throw snap_communicator_runtime_error("sigprocmask() failed to block signal.");
}
// second we create a "socket" for the signal (really it is a file
// descriptor manager by the kernel)
//
f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
if(f_socket == -1)
{
int const e(errno);
SNAP_LOG_ERROR("signalfd() failed to create a signal listener for signal ")(f_signal)(" (errno: ")(e)(" -- ")(strerror(e))(")");
throw snap_communicator_runtime_error("signalfd() failed to create a signal listener.");
}
// mark this signal as in use
//
sigaddset(&g_signal_handlers, f_signal); // ignore error, we already know f_signal is valid
}
With that socket you can do a poll()
and it triggers an equivalent to a read event when a signal arrives.
You retrieve the signal information like this:
int const r(read(f_socket, &f_signal_info, sizeof(f_signal_info)));
with f_signal_info
being declared as:
struct signalfd_siginfo f_signal_info;
The great thing is that now all of that happens in my classes and I don't have any weird signal handlers which can get my thread locked up, does not handle C++ exceptions correctly, and other potential problems. On my end, I have C++ classes with virtual functions that get called whenever an event occurs, including Unix signals such as SIGINT
, SIGPIPE
, SIGCHLD
... You could also implement callbacks using boost signals which give you the power of using std::bind()
.
All of that said, I still use signal()
for SIGSEGV
, SIGBUS
, etc. those that I'm not going to do any extra work when they occur. I try to log the stack trace on those and that's it. So I don't use signalfd()
on them (see my ed::signal_handler implementation).
¹ The old implementation was part of the snap_communicator environment and the signal class was around line 2789 at time of writing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With