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 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).
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.
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