Handle signals with epoll_wait and signalfd
Asked Answered
C

3

8

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?

Continental answered 4/4, 2017 at 15:55 Comment(2)
From the Fine Manual: 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.Recollect
Thanks, using sigprocmask solved all the problems!Continental
V
5

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).
Vagal answered 4/4, 2017 at 16:12 Comment(4)
Thanks for your answer and for reference to man! But I'm still having troubles handling the signal. I have epoll_wait syscall, when signalfd is in epoll. And then, when I send the signal from bash, it seems like the program is not executed further. So if (return_value_of_epoll_wait == -1) condition is never checked, when the signal is sent. How can I deal with this problem? Huge thanks in advance!Continental
@AleksandrTukallo Added a note for you about signal handler.Vagal
if signals masked then signalfd is perfectly pollable also there is epoll_pwait that accepts signal masks. poll(2), select(2) (and similar) The file descriptor is readable (the select(2) readfds argument; the poll(2) POLLIN flag) if one or more of the signals in mask is pending for the process. The signalfd file descriptor also supports the other file- descriptor multiplexing APIs: pselect(2), ppoll(2), and epoll(7).Bernoulli
@Bernoulli Not clear what you are trying to say. Add your own answer.Vagal
B
2

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
    }
Broadcasting answered 26/4, 2021 at 11:12 Comment(0)
C
1

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.

Cherlynchernow answered 13/6, 2019 at 17:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.