简体   繁体   中英

Handle signals with epoll_wait and signalfd

I'm writing my own echo server using sockets and syscalls. I am using epoll to work with many different clients at the same time and all the operations done with clients are nonblocking. When the server is on and doing nothing, it is in epoll_wait . Now I want to add the possibility to shut the server down using signals. For example, I start the server in bash terminal , then I press ctrl-c and the server somehow handles SIGINT . My plan is to use signalfd . I create new signalfd and add it to epoll instance with the following code:

    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGINT);
    signal_fd = signalfd(-1, &mask, 0);

    epoll_event event;
    event.data.fd = signal_fd;
    event.events = EPOLLIN;
    epoll_ctl(fd, EPOLL_CTL_ADD, signal_fd, &event);

Then I expect, that when epoll is waiting and I press ctrl-c , event on epoll happens, it wakes up and then I handle the signal with the following code:

    if (events[i].data.fd == signal_fd)
    {
        //do something
        exit(0);
    }

Though in reality the server just stops without handling the signal. What am I doing wrong, what is the correct way to solve my problem? And if I'm not understanding signals correctly, what is the place, where the one should use signalfd ?

epoll_wait returns -1 and errno == EINTR when it is interrupted by a signal. In this case you need to read from signal_fd .

Set the signal handler for your signals to SIG_IGN , otherwise signals may terminate your application.

See man signal 7 :

The following interfaces are never restarted after being interrupted by a signal handler, regardless of the use of SA_RESTART ; they always fail with the error EINTR when interrupted by a signal handler:

  • File descriptor multiplexing interfaces: epoll_wait(2), epoll_pwait(2), poll(2), ppoll(2), select(2), and pselect(2).

Though in reality the server just stops without handling the signal. What am I doing wrong, what is the correct way to solve my problem? And if I'm not understanding signals correctly, what is the place, where one should use signalfd?

Signal handlers are per process. You left the signal handler at the default, which is to terminate the processes.

So you need to add something like this,

struct sigaction action;
std::memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = your_handler;
sigaction(signum, &action, NULL);

for each signum that you want your application to receive interrupts for. Also handle the return value of sigaction. My experience is that if you use SIG_IGN as handler than you still interrupt a system call like epoll_pwait from the "outside", but it won't work when you try to wake up the thread from the program itself by sending the signal directly to that thread using pthread_kill .

Next you need to mask all signals from every thread, so that by default no thread will receive it (otherwise a random thread is woken up to handle the signal). The easiest way to do that is by doing it in main before creating any thread.

For example,

sigset_t all_signals;
sigemptyset(&all_signals);
sigaddset(&all_signals, signum); // Repeat for each signum that you use.
sigprocmask(SIG_BLOCK, &all_signals, NULL);

And then unblock the signals per thread when you want that thread to receive the signal.

If you use signalfd , then you do not want to unblock them - that system call unblocks the signals itself, just pass the appropriate mask (set bits for signalfd (it uses the passed mask to unblock). See also the man page of signalfd).

epoll_pwait works differently; like pselect you unblock the signal that you are interested in. You set a handler for that signal (see above) that sets a flag. Then just before calling epoll_pwait you block the signal, then test the flag and handle it, and then call epoll_pwait without first unblocking the signal. After epoll_wait returns you can unblock the signal again so that your handler can be called again.

You have to block all the signals you want to handle with your signal-FD before you create that signal-FD. Otherwise, those signals still interrupt blocked system calls such as epoll_wait() - as you observed.

See also the signalfd(2) man page :

Normally, the set of signals to be received via the file descriptor should be blocked using sigprocmask(2) , to prevent the signals being handled according to their default dispositions.

Thus, you have to change your example like this:

    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGTERM);
    sigaddset(&mask, SIGINT);
    int r = sigprocmask(SIG_BLOCK, &mask, 0);
    if (r == -1) {
        // XXX handle errors
    }
    signal_fd = signalfd(-1, &mask, 0);
    if (signal_fd == -1) {
        // XXX handle errors
    }

    epoll_event event;
    event.data.fd = signal_fd;
    event.events = EPOLLIN;
    r = epoll_ctl(fd, EPOLL_CTL_ADD, signal_fd, &event);
    if (r == -1) {
        // XXX handle errors
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM