Reputation: 453
To ensure that all destructors are properly called if the program is terminated from keyboard (Ctrl+C), the approach with signals are used:
accept()
, read()
, connect()
, etc) is waiting for completion, it returns -1 and errno
is set to EINTR
The problem is that SIGINT
can arrive between check for exit flag (while (!finish)
) and calling read()
. In this case, read()
will be blocked until the signal is sent once again.
This is a minimal working example:
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
enum { STDIN, STDOUT, STDERR };
static unsigned char finish=0;
static void handleSignal(int signal) {
finish=1;
}
int main(int argc, char ** e) {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler=handleSignal;
action.sa_flags=0;
sigaction(SIGINT, &action, NULL);
char buffer[256];
puts("<<");
while (!finish) {
sleep(2);
ssize_t n=read(STDIN, buffer, sizeof(buffer));
if (n==0) {
// End of stream
finish=1;
}
else if (n<0) {
// Error or interrupt
if (errno!=EINTR)
perror("read");
}
else {
// Convert data to hexadecimal format
for (size_t i=0; i<n; i++)
printf("%02x", buffer[i]);
}
}
puts(">>\n");
return 0;
}
sleep(2)
is added for visibility (a real program may perform some preparational work before reading from file descritor).
If there any way of reliable handling of signals without using non-crossplatform things like signalfd()
?
Upvotes: 2
Views: 693
Reputation: 58473
The pselect(2)
system call was invented to solve this exact problem. It's POSIX, so hopefully cross-platform enough for you.
The purpose of pselect
is to atomically unblock some signals, wait for I/O as select()
does, and reblock them. So your loop can look something like the following pseudocode:
sigprocmask(SIG_BLOCK, {SIGINT});
while (1) {
if (finish)
graceful_exit();
int ret = pselect(1, {STDIN}, ..., { /* empty signal set */});
if (ret > 0) {
read(STDIN, buf, size); // will not block
// process data
// If you like you can do
sigprocmask(SIG_UNBLOCK, {SIGINT});
// work work work
if (finish)
graceful_exit();
// work work work
sigprocmask(SIG_BLOCK, {SIGINT});
} else {
// handle timeout or other errors
}
}
There is no race here because SIGINT
is blocked for the time in between checking the finish
flag and the call to pselect
, so it cannot be delivered during that window. But the signal is unblocked while pselect
is waiting, so if it arrives during that time (or already arrived while it was blocked), pselect
will return without further delay. We only call read
when pselect
has told us it was ready for reading, so it cannot block.
If your program is multithreaded, use pthread_sigmask
instead of sigprocmask
.
As was noted in comments, you have to make your finish
flag volatile, and for best compatibility it should be of type sig_atomic_t
.
There is more discussion and another example in the select_tut(2)
man page.
Upvotes: 2