1

I'm writing a C program on Linux, where I have a main() and two pthreads created by it. In one of the pthreads I have a call to accept() function.

I have a signal handler which is invoked upon receiving of SIGINT, SIGQUIT or SIGTERM.

My expectation is, because I am making SA_RESTART flag zero, when I press ctrl-c, accept() function should return EINTR instead of getting restarted, however I realised via bunch of printf calls while debugging (to see which lines are executed by printing where the code is), that even though my application is able to catch the SIGINT, the accept function remains blocked, it is not failing with EINTR and not moving to next line of code. Here are my settings inside main()

struct sigaction signal_action;
signal_action.sa_flags = 0; // Don't restart the blocking call after it failed with EINTR
signal_action.sa_handler = terminate;
sigemptyset(&signal_action.sa_mask);
sigfillset(&signal_action.sa_mask); // Block every signal during the handler is executing
if (sigaction(SIGINT, &signal_action, NULL) < 0) {
    perror("error handling SIGINT");
}
if (sigaction(SIGTERM, &signal_action, NULL) < 0) {
    perror("error handling SIGTERM");
}
if (sigaction(SIGQUIT, &signal_action, NULL) < 0) {
    perror("error handling SIGQUIT");
}

Here is the signal handler:

void terminate (int signum)
{
    terminate_program = 1;
    printf("Terminating.\n");
}

Here is the pthread where the accept() call is made (I tried to delete unrelated stuff to make my question more understandable):

void* pthread_timerless_socket_tasks(void* parameter)
{

    server_socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(server_socket_fd < 0)
    {
        perror("error creating IPv4 TCP stream socket");
        return (NULL);
    }
    printf("socket created\n"); // for debugging
    if(fcntl(server_socket_fd, F_SETFL, 0) < 0)
    {
        perror("error making socket_fd blocking");
        close(server_socket_fd);
        return (NULL);
    }

    while(!terminate_program)
    {
        printf("socket blocking on accept\n"); // for debugging
        client_socket_fd = accept(server_socket_fd,(struct sockaddr *) &client_address, &client_length);
        printf("socket accepted?\n"); // for debugging
        if(client_socket_fd < 0)
        {
            perror("error accepting socket_fd");
            close(server_socket_fd);
            return (NULL);
        }


    } 

I hope I made myself clear.

So, now I am wondering, what is missing or incorrect as I am unable to see the behaviour described in the linux manuals for SA_RESTART.

etugcey
  • 109
  • 2
  • 11
  • This `if(fcntl(server_socket_fd, F_SETFL, 0) ...` looks strange, to not say wrong. – alk Apr 15 '16 at 11:15
  • @alk I was trying to make the file descriptor blocking this way, as by default it wasn't. I'm open to suggestions:) – etugcey Apr 15 '16 at 13:07
  • On how to toggle to the blocking-state of a socket using `fcntl()` you might like to have looks this answer: http://stackoverflow.com/a/1549344/694576 – alk Apr 15 '16 at 15:22

1 Answers1

5

From the signal(7) manual page:

If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which to deliver the signal.

What that means is that if the signal is not sent to the thread doing the accept, then the call won't be interrupted by the signal.

You should block (by setting the signal mask) the signals in all other threads, then the only thread that can receive the signal is the one calling accept.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • I moved the "settings" code (where I call the sigaction and set sa_flags to zero) from the main() to the thread with accept() (and made sure no other thread has this code), to make sure the signal is delivered to the right thread but still accept() is blocking. – etugcey Apr 15 '16 at 11:16
  • I realised from your post, I need to block other threads too, I'll try it now. – etugcey Apr 15 '16 at 11:21
  • @etugcey Open the linked manual page, and search for "Signal mask and pending signals". That will tell you which functions you need to block and unblock signals per process and per thread. – Some programmer dude Apr 15 '16 at 11:23
  • Being new to linux programming, now I am confused how to terminate my programs with SIGINT if more than one thread has a blocking function call; because my first intention was to have while loops in each pthread, make them exit their while loop when the terminate_program flag is set (this happens inside the signal handler), and inside the thread after the while loop, clean-up stuff. Now it seems like I should do all the clean-up of all the code in one thread where the signal is not masked (and mask the signal in all other threads). – etugcey Apr 15 '16 at 11:37
  • @etugcey Since the `terminate_program` flag is a global (I guess) variable all threads that are not otherwise blocked (do you have more threads that does blocking calls? Consider the [`pthread_kill`](http://man7.org/linux/man-pages/man3/pthread_kill.3.html) or [`pthread_cancel`](http://man7.org/linux/man-pages/man3/pthread_cancel.3.html) functions) then they should all exit. Even the main thread, the one that started it all, should check for the flag, and when set start to wait for the threads to exit. Then each thread clean up after itself, and the main process cleans up the rest. – Some programmer dude Apr 15 '16 at 11:43
  • 1
    @etugcey welcome to pthreads. Stopping threads with blocking system calls cleanly is one of the hardest problems there is. Your next idea will be to look at `pthread_cancel`. Ignore that. It will cause you even more trouble. The best way I've found to do this is to not block on anything other than `poll` and have each thread `poll` on a file descriptor (eventfd on linux, pipe on every other system) while also polling on the other file descriptor (like your listen socket) and use that to signal the threads that they need to exit (or do something else). – Art Apr 15 '16 at 11:43
  • 1
    @art ... or install a single (process-wide) thread catching all signals and let the latter dispatch the signal received as needed using `pthread_kill()`. – alk Apr 15 '16 at 12:01
  • @alk The keyword was "cleanly". `pthread_kill` isn't clean. I agree that it's sufficient in 99% of the cases, but I've had situations where I actually need to finish certain things in my threads before I let them die. – Art Apr 15 '16 at 12:12
  • @Art [`pthread_kill`](http://man7.org/linux/man-pages/man3/pthread_kill.3.html) is like the [`kill`](http://man7.org/linux/man-pages/man2/kill.2.html) system call: It doesn't actually *kill* anything, all it does is send a signal to the specific thread. – Some programmer dude Apr 15 '16 at 12:15
  • @JoachimPileborg yeah. Brainfart. It's Friday and my head interpreted is as `pthread_cancel`. Still, `pthread_kill` to cleanly stop your threads is impossible to use when you're a library and don't control all the signal masks of all the threads in a program. Just a month ago I had this problem, turned some functionality of a program (ptrace of processes and their children) into a threaded library, ended up not being able to control the signal masks in all my users and ended up rewriting everything into poll+eventfd like I mentioned before. – Art Apr 15 '16 at 12:24
  • I think I sort of get what you are saying. I need to read more into this poll function to understand you fully but, are you saying that, instead of using a blocking call directly with a function like accept(), read() etc. i should block on any file descriptor with poll() since it can keep track of more than one and with it I can also unblock when other file descriptors become ready. So I guess, if I can somehow make another file descriptor ready when the SIGINT occurs, the poll can unblock. – etugcey Apr 15 '16 at 12:28
  • using pthread_kill to send SIGINT, totally worked for me. Thank you very much all, it was very helpful for me indeed in terms of giving more thought into the subject and grasping the signal and pthreads more. I will also look more into the detail of the poll() solution suggested by Art. – etugcey Apr 15 '16 at 13:00