I have a problem with this simple server code, it works as expected until it receives a signal. For debug I print server and client file descriptors before calling select with the line:
fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%dn", server_socket_fd, client_socket_fd, fd_max);
When running normally it keeps printing
server_socket_fd=3 client_socket_fd=4 fd_max=4
but when it receives a signal it prints this line once
server_socket_fd=3 client_socket_fd=-1 fd_max=3
and then the program stalls.
Using GDB I put a breakpoint in the signal_handler and when it breaks I can’t watch client_socket_fd variable, gdb says
No symbol "client_socket_fd" in current context.
And it doesn’t return properly from the signal_handler function.. if I watch the back trace:
(gdb) bt
#0 0xb7fdccf9 in ?? ()
#1 0xb7e26af3 in __libc_start_main (main=0x8048bdd <main>, argc=1, argv=0xbfffef24, init=0x8049a00 <__libc_csu_init>,
fini=0x8049a70 <__libc_csu_fini>, rtld_fini=0xb7fed160 <_dl_fini>, stack_end=0xbfffef1c) at libc-start.c:287
#2 0x08048b01 in _start ()
I don’t know how to debug deeper.
This is the main code:
char receive_buf[2048];
int main(int argc, char *argv[]){
int server_socket_fd;
int client_socket_fd = -1;
int fd_max;
struct sockaddr_in s_in;
int one = 1;
int status;
fd_set readfds;
int port;
int next_option;
const char* short_options = "hp:d:";
const struct option long_options[] = {
{ "help", 0, NULL, 'h'},
{ "port", 1, NULL, 'p'},
{ "debug", 1, NULL, 'd'},
{ NULL, 0, NULL, 0}
};
program_name = argv[0];
port = DEFAULT_PORT;
debug = 0;
do{
next_option = getopt_long(argc, argv, short_options, long_options, NULL);
switch(next_option){
case 'h':
print_usage(stdout, 0);
break;
case 'p':
port = atoi(optarg);
if((port < 0)||(port > 65535)){
fprintf(stderr, "Invalid port number (%d), using default: %d", port, DEFAULT_PORT);
port = DEFAULT_PORT;
}
break;
case 'd':
debug = atoi(optarg);
if(debug < 0 || debug > 3)
debug = 0;
break;
case '?':
print_usage(stderr, 1);
break;
case -1:
break;
default:
abort();
}
}while(next_option != -1);
/************************* SIGNAL DEFINITIONS ***************************/
signal_action.sa_handler = (void *)signal_handler;
sigemptyset(&signal_action.sa_mask);
signal_action.sa_flags = SA_RESTART; // | SA_NOCLDSTOP;
if(sigaction(SIGINT, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGINT signal handlern");
exit(1);
}
if(sigaction(SIGTERM, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGTERM signal handlern");
exit(1);
}
if(sigaction(SIGWINCH, &signal_action, NULL) == -1){
fprintf(stderr, "Error setting SIGWINCH signal handlern");
exit(1);
}
/* // ALSO TRIED WITH SIGNAL WITH SAME RESULT
if(signal(SIGWINCH, signal_handler) == SIG_ERR){
fprintf(stderr, "signal errorn");
return 1;
}
*/
s_in.sin_family = PF_INET;
s_in.sin_port = htons(port);
s_in.sin_addr.s_addr = INADDR_ANY;
if ((server_socket_fd = socket(s_in.sin_family, SOCK_STREAM, IPPROTO_TCP)) == -1){
perror("Error creating socket");
return 1;
}
if(setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1){
perror("Error setting socket parameters");
return 1;
}
////////////////////////////////////////////
int x;
x=fcntl(server_socket_fd,F_GETFL,0); // Get socket flags
fcntl(server_socket_fd,F_SETFL,x | O_NONBLOCK); // Add non-blocking flag
////////////////////////////////////////////
if(bind(server_socket_fd, (struct sockaddr*) &s_in, sizeof(s_in)) == -1){
perror("Error binding socket");
return 1;
}
if(listen(server_socket_fd, 1) == -1){
perror("Error creating listening socket");
return 1;
}else{
printf("Server (%d) listening on port %dn", server_socket_fd, port);
}
memset(receive_buf, '', sizeof(receive_buf));
gettimeofday(&t_print, NULL);
while(1){
// SERVER
FD_ZERO(&readfds);
FD_SET(server_socket_fd, &readfds);
fd_max = server_socket_fd;
// ADD CLIENT IF CONNECTED
if(client_socket_fd > 0){
FD_SET(client_socket_fd, &readfds);
if(client_socket_fd > server_socket_fd)
fd_max = client_socket_fd;
}
// ADDED THIS FPRINTF TO CHECK VARIABLES <----------------------------------
fprintf(stderr,"server_socket_fd=%d client_socket_fd=%d fd_max=%dn", server_socket_fd, client_socket_fd, fd_max);
if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
if(errno != EINTR){
perror("select failed");
}
}
// ACCEPT CLIENT
if(FD_ISSET(server_socket_fd, &readfds)){
struct sockaddr_in s_in;
socklen_t len;
len = sizeof(s_in);
if((client_socket_fd = accept(server_socket_fd, (struct sockaddr*) &s_in, &len)) < 0){
if(errno != EWOULDBLOCK){
perror("En accept");
}
}else
printf("New client connected from %sn", inet_ntoa(s_in.sin_addr));
}
// RECEIVE FROM CLIENT
if(client_socket_fd > 0){
if(FD_ISSET(client_socket_fd, &readfds)){
handle_client(client_socket_fd);
}
}
}
return 0;
}
int handle_client(int cl_fd){
int n;
n = recv(cl_fd, receive_buf, sizeof(receive_buf) - 1, MSG_DONTWAIT);
if(n == 0){
fprintf(stderr,"--------------> DEBUG: handle_client:client %d closed connectionn", cl_fd);
}else if(n < 0){
if(errno == EAGAIN){
return 0;
}else{
fprintf(stderr,"--------------> DEBUG: handle_client: recv ERROR: client %d closed connection (errno: %d : %s)n", cl_fd, errno, strerror(errno));
memset(receive_buf, 0, sizeof(receive_buf));
return -1;
}
}else{
receive_buf[n] = '';
fprintf(stderr, "%sn", receive_buf);
}
return 0;
}
void signal_handler(int sig){
switch(sig){
case SIGINT:
exit_properly(0);
break;
case SIGTERM:
exit_properly(1);
break;
case SIGABRT:
fprintf(stderr, "SIGABRT signal receivedn");
break;
case SIGWINCH:
fprintf(stderr, "33[2J");
fflush(stdout);
break;
default:
fprintf(stderr, "Unhandled signal %d receivedn",sig);
break;
}
}
I don’t know what else can I do for debugging this issue and I am stuck. Any help will be very appreciated!
EDITED:
This is the strace output when it fails, as you can see it prints (and select uses) the right file descriptors and then, after the signal occurs, the client_socket_fd is wrong because accept fails with EAGAIN. I have commented the exit_properly calls and also the signal handling for SIGTERM and SIGINT. For the SIGWINH signal I do nothing, just return.
STRACE output:
write(2, "server_socket_fd=3 client_socket" , 47server_socket_fd=3 client_socket_fd=4 fd_max=4
) = 47
select(5, [3 4], NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask []) = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16]) = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket" , 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL) = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
sigreturn() (mask []) = -1 EINTR (Interrupted system call)
accept(3, 0xbf981e7c, [16]) = -1 EAGAIN (Resource temporarily unavailable)
write(2, "server_socket_fd=3 client_socket" , 48server_socket_fd=3 client_socket_fd=-1 fd_max=3
) = 48
select(4, [3], NULL, NULL, NULL
The signal_handler now:
void signal_handler(int sig){
switch(sig){
/*
case SIGINT:
exit_properly(0); //sigint_flag = 1;
break;
case SIGTERM:
exit_properly(1); //sigterm_flag = 1;
break;
*/
case SIGWINCH:
//sigwinch_flag = 1;
/*
fprintf(stderr, "33[2J");
fflush(stdout);
*/
break;
default:
//fprintf(stderr, "Unhandled signal %d receivedn",sig);
break;
}
}
Also tried without the SA_RESTART
flag… same result… ?: /
Advertisement
Answer
select()
will be interrupted by a signal, return -1, and set errno to EINTR, but your code fails to handle this.
Even if you install a signal handler with the SA_RESTART
, there are still a number of system calls that will be interrupted, return an error condition and set errno to EINTR
.
See the “Interruption of system calls and library functions by signal handlers” section at http://man7.org/linux/man-pages/man7/signal.7.html/
If select fails, your code goes on to check the readfds
like this:
if(FD_ISSET(server_socket_fd, &readfds)){
However, if select() fails, the fd_set
variables you pass to it are in an indeterministic state, you should not rely on their values.
Instead, if select fails you should just re-start your loop with e.g. a continue statement like so:
if(select(fd_max+1, &readfds, NULL, NULL, NULL) == -1){
if(errno != EINTR){
perror("select failed");
//Might be severe enough to quit your program...
}
continue;
}