Skip to content
Advertisement

Linux server C code stalls after receiving a signal

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; 
    }
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement