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; }