Search code examples
csocketssignalsmultiplexing

C: select() - Signal interrupt


I'm writing a multithreaded server program in C that works with AF_UNIX sockets. The basic structure of the server is:

  • Main thread initialize data structures and spears a pool of "worker" threads.
  • Worker threads start waiting for new requests on an empty thread-safe queue
  • Main thread listen on various sockets (new connection and already connected clients) with a select() call.
    • select() reveals possible read on connection socket: main thread calls accept() and puts the returned file descriptor in the fd_set (read set).
    • select() reveal possible read on already connected sockets: main thread removes the ready file descriptors from the fd_set (read set) and puts them in the thread-safe queue.
  • Worker thread extracts a file descriptor from the queue and starts to communicate with the linked client for serve the request. At the end of the service worker thread puts socket file descriptor back to the fd_set (i worte a function to make this operation thread-safe) and it returns waiting again on the queue for a new request.

This routine is repeated in a infinite cycle until a SIGINT is raised. Another function has to be performed on SIGUSR1 without exiting from the cycle.

My doubt is about this because if I raise a SIGINT my program exit with EINTR = Interrupted system call. I know about the pselect() call and the "self pipe" trick but i can't figure out how to make the things work in a multithreaded situation.

I'm looking for a (POSIX compatible) signal management that that prevent the EINTR error while main thread is waiting on pselect().

I post some pieces of code for clarification:

Here i set up signal handlers (ignore errorConsolePrint function)

if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}
if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting SIGINT handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}
if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting to SIGUSR1 handler", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}

if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
{
    errorConsolePrint("File: %s; Line: %d; ", "Setting to ignore SIGPIPE", __FILE__, __LINE__);
    exit(EXIT_FAILURE);
}

Here i set up signal mask for pselect

sigemptyset(&mask);
sigemptyset(&saveMask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGPIPE);

Here i call pselect

test = saveSet(masterSet, &backUpSet, &saveMaxFd);
CHECK_MINUS1(test, "Server: creating master set's backup ");

int test = pselect(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting, &mask);
if(test == -1 && errno != EINTR)
{
    ...error handling...
    continue;
}

Hope in some help! Thank you all in advance.


Solution

  • Ok, finally I got a solution.

    The heart of my problem was about the multithreading nature of my server. After long search I found out that in the case we have signals raised from other process (in an asyncronous way), it doens't matter which thread capture signal because the behaviour remains the same: The signal is catched and the previously registered handler is executed. Maybe this could be obvious for others but this was driving me crazy because I did not know how to interpret errors that came out during execution.

    After that i found another problem that I solved, is about the obsolete signal() call. During execution, the first time i rise SIGUSR1, the program catch and manage it as expected but the second time it exit with User defined signal 1.

    I figured out that signal() call set "one time" handler for a specific signal, after the first time that the signal is handled the behaviour for that signal return the default one.

    So here's what I did:

    Here the signal handlers:

    N.B.: I reset handler for SIGUSR1 inside the handler itself

    static void on_SIGINT(int signum)
    {
        if(signum == SIGINT || signum == SIGTERM)
            serverStop = TRUE;
    }
    
    static void on_SIGUSR1(int signum)
    {
        if(signum == SIGUSR1)
            pendingSIGUSR1 = TRUE;
    
        if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
            exit(EXIT_FAILURE);
    }
    

    Here I set handlers during server's initialization:

     if(signal(SIGINT, &on_SIGINT) == SIG_ERR)
        exit(EXIT_FAILURE);
    if(signal(SIGTERM, &on_SIGINT) == SIG_ERR)
        exit(EXIT_FAILURE);
    if(signal(SIGUSR1, &on_SIGUSR1) == SIG_ERR)
        exit(EXIT_FAILURE);
    if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)
        exit(EXIT_FAILURE);
    

    And here the server's listening cycle:

    while(!serverStop)
    {
        if (pendingSIGUSR1)
        {
            ... things i have to do on SIGUSR1...
            pendingSIGUSR1 = FALSE;
        }
    
    
        test = saveSet(masterSet, &backUpSet, &saveMaxFd);
        CHECK_MINUS1(test, "Server: creating master set's backup ");
    
        int test = select(saveMaxFd+1, &backUpSet, NULL, NULL, &waiting);
        if((test == -1 && errno == EINTR) || test == 0)
            continue;
        if (test == -1 && errno != EINTR)
        {
            perror("Server: Monitoring sockets: ");
            exit(EXIT_FAILURE);
        }
    
        for(int sock=3; sock <= saveMaxFd; sock++)
        {
            if (FD_ISSET(sock, &backUpSet))
            {
                if(sock == ConnectionSocket)
                {
                    ClientSocket = accept(ConnectionSocket, NULL, 0);
                    CHECK_MINUS1(ClientSocket, "Server: Accepting connection");
    
                    test = INset(masterSet, ClientSocket);
                    CHECK_MINUS1(test, "Server: Inserting new connection in master set: ");
                }
                else
                {
                    test = OUTset(masterSet, sock);
                    CHECK_MINUS1(test, "Server: Removing file descriptor from select ");
                    test = insertRequest(chain, sock);
                    CHECK_MINUS1(test, "Server: Inserting request in chain");
                }
             }
        }
    }