Reputation: 1427
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
?
Upvotes: 8
Views: 8862
Reputation: 39085
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
}
Upvotes: 2
Reputation: 6801
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.
Upvotes: 1
Reputation: 136286
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 errorEINTR
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).
Upvotes: 5