7

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++?

motam79
  • 3,542
  • 5
  • 34
  • 60
  • Is the last line of your last-but-one code box right? I suspect you should have written `my_handler` instead of `terminate_or_interrupt_handler` – L. F. Aug 25 '18 at 00:17
  • my_class_signal_flag is the variable defined in another class that I am trying to bind it to the general signal handler. – motam79 Aug 25 '18 at 00:24
  • There are many things you can't do in a signal handler, and throwing exceptions is one of them. – molbdnilo Aug 25 '18 at 06:27

2 Answers2

4

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.

L. F.
  • 19,445
  • 8
  • 48
  • 82
2

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.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156