2

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.

  • I'm not quite following what it is that you actually want to know. How to provide different handling for the `SIGINT` and `SIGUSR1` cases? How to terminate a multithreaded process? How to *gracefully* terminate a multithreaded process? Something else? – John Bollinger Jan 31 '19 at 22:44
  • Where in your program does the EINTR error happen? – user253751 Feb 01 '19 at 00:27
  • My question is: How can i configure signal management to prevent error "Interrupted system call" when process is waiting on `select()` call – Alessandro Meschi Feb 01 '19 at 08:45
  • The basic plan is seriously flawed. Strongly suggest: in `main()` setup the thread pool, setup the signal handler, call socket(), call any `setsockopt()`s, call `bind()` call `listen()` Then, in a loop call `accept()` then pass the socket returned from `accept()` to an available thread. The thread then handles all the communication with the client including finally closing the socket – user3629249 Feb 01 '19 at 22:27
  • @user3629249 why do you think that this structure is flawed? The one that you suggest is unseemly because the client (that is given, i can not modify it) can wait a lot of time between two request, in this way a thread can be occupied indefinitely. – Alessandro Meschi Feb 02 '19 at 13:28
  • the command: `select()` does not give the client a unique socket. and unless the socket is closed and re-generated, it will not let go of the client connection for an extended period of time. To have multiple clients connecting at the same time, the original socket, returned from the function: `socket()` must be available for the next client to use – user3629249 Feb 03 '19 at 02:32
  • @user3629249 I am not sure I understand what you are saying, anyway in the server's initialization i create a socket with `socket()` then i call `bind()` with a `struct sockaddr_un` where I set the path and the family. Before the infinite cycle i call `listen()` on that socket and i insert it in the `fd_set` which the `select()` monitors for possible reads. You can read what happens in the cycle on the answer at my own question below. – Alessandro Meschi Feb 03 '19 at 10:50

4 Answers4

1

What you should probably do is dedicate a thread to signal handling. Here's a sketch:

In main, before spawning any threads, block all signals (using pthread_sigmask) except for SIGILL, SIGABRT, SIGFPE, SIGSEGV, and SIGBUS.

Then, spawn your signal handler thread. This thread loops calling sigwaitinfo for the signals you care about. It takes whatever action is appropriate for each; this could include sending a message to the main thread to trigger a clean shutdown (SIGINT), queuing the "another function" to be processed in the worker pool (SIGUSR1), etc. You do not install handlers for these signals.

Then you spawn your thread pool, which doesn't have to care about signals at all.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Yes I read about `signalfd()` and `sigwaitinfo() `but they are linux only. I forgot to write it in the question, i'm sorry. Any other solution? – Alessandro Meschi Feb 01 '19 at 08:51
  • 2
    [`sigwaitinfo` is POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/sigtimedwait.html). – zwol Feb 01 '19 at 13:07
  • @zwol You are right. I got compilation error because i'm compiling with `-std=c99` option – Alessandro Meschi Feb 01 '19 at 16:48
  • @AlessandroMeschi For future reference, in almost all cases when working with GCC, `-std=gnuXX -Wall -Wpedantic` is a better way to avoid unwanted extensions than `-std=cXX`. The extensions disabled by the `-std=cXX` modes are so ubiquitously used that I have repeatedly discovered bugs in system headers just by compiling perfectly ordinary code with them. – zwol Feb 01 '19 at 16:53
  • @zwol you are damn right but this project is for an university exam and the Makefile that compile all the sources can not be modified! This thing is driving me crazy! – Alessandro Meschi Feb 01 '19 at 17:05
  • 1
    @AlessandroMeschi In that case, `#define _XOPEN_SOURCE 700` at the top of all your C source files (before including _any_ system headers) to get access to everything standardized by the current rev of POSIX. (Caution: on some systems this will _remove_ access to older things that you may want, e.g. `gettimeofday` and `setitimer` (officially superseded by the `clock_*` interfaces but there are a couple of things `setitimer` can do that they can't).) – zwol Feb 01 '19 at 17:07
  • @zwol Great trick! thanks very much! this will help me a lot! Now I can try to code your solution. – Alessandro Meschi Feb 01 '19 at 17:12
1

I would suggest the following strategy:

  • During initialization, set up your signal handlers, as you do.
  • During initialization, block all (blockable) signals. See for example Is it possible to ignore all signals?.
  • Use pselect in your main thread to unblock threads for the duration of the call, again as you do.

This has the advantage that all of your system calls, including all those in all your worker threads, will never return EINTR, except for the single pselect in the main thread. See for example the answers to Am I over-engineering per-thread signal blocking? and pselect does not return on signal when called from a separate thread but works fine in single thread program.

This strategy would also work with select: just unblock the signals in your main thread immediately before calling select, and re-block them afterwards. You only really need pselect to prevent hanging if your select timeout is long or infinite, and if your file descriptors are mostly inactive. (I've never used pselect myself, having worked mostly with older Unix's which did not have it.)

I am presuming that your signal handlers as suitable: for example, they just atomically set a global variable.

BTW, in your sample code, do you need sigaddset(&mask, SIGPIPE), as SIGPIPE is already ignored?

Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77
1

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");
            }
         }
    }
}
0

Read first signal(7) and signal-safety(7); you might want to use the Linux specific signalfd(2) since it fits nicely (for SIGTERM & SIGQUIT and SIGINT) into event loops around poll(2) or the old select(2) (or the newer pselect or ppoll)

See also this answer (and the pipe(7) to self trick mentioned there, which is POSIX-compatible) to a very similar question.

Also, signal(2) documents:

  The effects of signal() in a multithreaded process are unspecified.

so you really should use sigaction(2) (which is POSIX).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Yes i have already read about `signalfd() ` and it would be perfect but i can not use linux specific calls, as I wrote in the question i need a POSIX compatible solution. – Alessandro Meschi Feb 02 '19 at 13:34
  • Yes i also read about it that is a very nice solution, but for now i solved as i wrote in the last answer to my own question. In that way everything seemes to work fine,. Could you read it an tell me what do you think about? – Alessandro Meschi Feb 02 '19 at 13:39