0

I'm writing a program in c, which make use of threads, and i also want to catch Ctrl+C signal from the user. So, before i go multithreading, i make the signal catching.

My main thread (i mean besides the actual main thread that the program runs on), is a method to deal with user input, and i also join this thread to the main program thread.

The problem is, when testing and hitting Ctrl+C to exit program, the thread responsible for receiving user input doesn't close until i hit "return" on my keyboard - like its stuck on infinite loop.

When exiting by typing 'q', all threads end up properly.

I use a global variable exit_flag to indicate the threads to finish their loops.

Also, in init_radio_stations method there's another single thread creation, that loops in the exact same way - on the exit_flag status, and this thread DOES close properly

Here's my main loop code:

void main_loop()
{
    status_type_t rs = SUCCESS;
    pthread_t thr_id;

    /* Catch Ctrl+C signals */
    if(SIG_ERR == signal(SIGINT, close_server)) {
        error("signal() failed! errno = ");
    }

    printf("\n~ Welcome to radio_server! ~\n Setting up %d radio stations... ", srv_params.num_of_stations);
    init_radio_stations();
    printf("Done!\n\n* Hit 'q' to exit the application\n* Hit 'p' to print stations & connected clients info\n");

    /* Create and join a thread to handle user input */
    if(pthread_create(&thr_id, NULL, &rcv_usr_input, NULL)) {
        error("main_loop pthread_create() failed! errno = ");
    }
    if(pthread_join(thr_id, NULL)) {
        error("main_loop pthread_join() failed! errno = ");
    }
}

close_server method:

void close_server(int arg)
{
    switch(arg) {
    case SIGINT: /* 2 */
        printf("\n^C Detected!\n");
        break;

    case ERR: /* -1 */
        printf("\nError occured!\n");
        break;

    case DEF_TO: /* 0 */
        printf("\nOperation timed-out!\n");
        break;

    default: /* will handle USER_EXIT, and all other scenarios */
        printf("\nUser abort!\n");
    }

    printf("Signaling all threads to free up all resources and exit...\n");

    /* Update exit_flag, and wait 1 sec just in case, to give all threads time to close */
    exit_flag = TRUE;
    sleep(1);
}

And rcv_usr_input handle code:

void * rcv_usr_input(void * arg_p)
{
    char in_buf[BUFF_SIZE] = {0};

    while(FALSE == exit_flag) {
        memset(in_buf, 0, BUFF_SIZE);

        if(NULL == fgets(in_buf, BUFF_SIZE, stdin)) {
            error("fgets() failed! errno = ");
        }

        /* No input from the user was received */
        if('\0' == in_buf[0]) {
            continue;
        }

        in_buf[0] = tolower(in_buf[0]);
        if( ('q' == in_buf[0]) && ('\n' == in_buf[1]) ) {
            close_server(USER_EXIT);
        } else {
            printf("Invalid input!\nType 'q' or 'Q' to exit only\n");
        }
    }

    printf("User Input handler is done\n");
    return NULL;
}

I'm guessing my problem is related to joining the thread that uses rcv_usr_input at the end of my main loop, but i can't figure out what exactly causing this behavior.

I'll be glad to get some help, Thanks

Adiel
  • 1,203
  • 3
  • 18
  • 31
  • Data race on unsynchronized access to `exit_flag`, written to by a signal handler (oh, boy! Mixing threads and signals, *what could go wrong*), undefined behavior. – EOF Feb 20 '16 at 21:07
  • 2
    @EOF No need for sarcasm, working with threads and signals is new to me and i'm still learning :) How would you suggest handling signals when working with threads? i'm always glad to learn new things – Adiel Feb 21 '16 at 06:52
  • The code has a few problems which you can research on this site. `pthread_create` returns an error code (it does not set `errno`). `exit_flag` probably needs to be `volatile sig_atomic_t`, but that won't help visibility issues _between threads_. `printf` is not async-signal-safe. `fgets` can return NULL upon error _and_ success (i.e., EOF). – pilcrow Feb 21 '16 at 19:11
  • @pilcrow thanks. i've changed error handling from `pthread_create`. i'll also change `exit_flag` to your suggestion, but do you mean by _that won't help visibility issues between threads_ ? fgets got replaced by read, and i'm not sure what "async-signal-safe" means. thanks a lot! – Adiel Feb 21 '16 at 20:08
  • @Adiel: `volatile sig_atomic_t` is not a thread-safe atomic. It only addresses the issue of sharing a variable between normal execution and a signal handler running in the same thread, not between threads. You need a C11 `_Atomic` for that, specifically one that fulfills the `atomic_is_lock_free()` predicate. Changing the type of `exit_flag` to `atomic_flag` should meet those requirements. i.e `atomic_flag exit_flag;`. – EOF Feb 22 '16 at 13:04
  • @EOF thanks for elaborating. could you please explain why would i need **atomic_is_lock_free()** in my case? **exit_flag** is only changed by a specific method (close_server) while all other threads are just 'looping' through its state – Adiel Feb 22 '16 at 16:27
  • @Adiel: C11 draft standard n1570 `5.1.2.4 Multi-threaded executions and data races, Section 4 Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location. Section 25 The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.` One thread writes, the other reads, no synchronization -> data race. – EOF Feb 22 '16 at 16:32
  • @Adiel: Since you're modifying in a signal handler, things get worse: `5.1.2.3 Program execution, Section 5 When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are neither lock-free atomic objects nor of type volatile sig_atomic_t are unspecified[...]. The value of any object modified by the handler that is neither a lock-free atomic object nor of type volatile sig_atomic_t becomes indeterminate when the handler exits[...]`. Modification of shared data in signal handler -> cannot use locking. – EOF Feb 22 '16 at 16:35
  • @EOF thannk you for the many usefule info. i tried looking up for `_Atomic` type to use as my exit_flag, but found out its only defined in **C++** . any equivalent i can use in C? Or other suggestion that could help making the `exit_flag` atomic_is_lock_free as you mentioned, without having the need to discard signal handling in my program ?? Thanks again for all your help, much appreciated – Adiel Feb 23 '16 at 06:07
  • @Adiel: Before C11/C++11, the two languages didn't have native support for threads or atomics. To use the new atomics in C, you need a C11 compiler. `gcc` and `clang` have supported C11 for a while now. Unfortunately, if you're using Microsoft's compiler, it is stuck back in the last millennium, supporting only ancient C89. It *does* support C++11 though, so if you're not using any C-only features (VLA, designated initializers,...), you can just use the C++ compiler. – EOF Feb 23 '16 at 12:35
  • @EOF i'm using `gcc`, under linux enviroment, does it mean i can use `atomic_` types that are defined in `stdatomic.h` without problems? my intention is to define **exit_flag** as `atomic_uchar` (which is typdefed as `atomic_uint_fast8_t` in `stdatomic.h`) – Adiel Feb 23 '16 at 16:25
  • @Adiel AFAIK, gcc only implements stdatomics in C since `gcc 4.9`. If you have an older gcc, you can possibly still use `C++11` atomics in `g++`. But *if* you have a version of gcc that supports atomics, you can declare `atomic_flag exit_flag` and have a guarantee that it is lock free (the standard requires it). If you use `_Atomic _Bool` or `atomic_bool`, you *technically* need to check `atomic_is_lock_free(&exit_flag)`, though I suspect there are *very* few real implementations where it is not. – EOF Feb 23 '16 at 16:30

3 Answers3

2

Mike and Kaylum have correctly identified the fundamental problem of blocking by fgets(). The larger issue remains, however: how to terminate a blocking thread when the process receives a SIGINT. There are several solutions.

Thead Detachment: One solution is to detach the blocking thread because detached threads do not prevent the process from terminating when the last non-detached thread terminates. A thread is detached either by calling pthread_detach() on it, e.g.,

#include <pthread.h>
// Called by pthread_create()
static void* start(void* arg)
{
    pthread_detach();
    ...
}

or by creating the thread with the PTHREAD_CREATE_DETACHED attribute, e.g.,

#include <pthread.h>
...
    pthread_attr_t attr;
    (void)pthread_attr_init(&attr);
    (void)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    ...
    (void)pthread_t thread;
    (void)pthread_create(&thread, &attr, ...);

Note that pthread_join() should not be called on a detached thread.

Signal Forwarding: Another solution is not to detach the blocking thread but to forward signals like SIGINT to the thread via pthread_kill() if the signal has not been received on the blocking thread, e.g.,

#include <pthread.h>
#include <signal.h>
...
static pthread_t thread;
...
static void handle_sigint(int sig)
{
    if (!pthread_equal(thread, pthread_self()) // Necessary
        (void)pthread_kill(thread, SIGINT);
}
...
    sigaction_t sigaction;
    sigaction.sa_mask = 0;
    sigaction.sa_flags = 0;
    sigaction.sa_handler = handle_sigint;
    (void)sigaction(SIGHUP, &sigaction, ...);
    ...
    (void)pthread_create(&thread, ...);
    ...
    (void)pthread_join(thread, ...);
    ...

This will cause the blocking function to return with errno set to EINTR.

Note that use of signal() in a multi-threaded process is unspecified.

Thread Cancellation: Another solution is to cancel the blocking thread via pthread_cancel(), e.g.,

#include <pthread.h>
...
static void cleanup(...)
{
    // Release allocated resources
    ...
}
...
static void* start(void* arg)
{
    pthread_cleanup_push(cleanup, ...);
    for (;;) {
        ...
        // Call the blocking function
        ...
    }
    pthread_cleanup_pop(...);
    ...
}
....
static void handle_sigint(int sig)
{
    (void)pthread_cancel(thread);
}
...
    sigaction_t sigaction;
    sigaction.sa_mask = 0;
    sigaction.sa_flags = 0;
    sigaction.sa_handler = handle_sigint;
    (void)sigaction(SIGHUP, &sigaction, ...);
    ...
    (void)pthread_create(&thread, ..., start, ...);
    ...
    (void)pthread_join(thread, ...);
    ...

There is yet another solution for threads that block in a call to select() or poll(): create a file descriptor on which the blocking function also waits and close that descriptor upon receipt of an appropriate signal -- but that solution is, arguably, beyond the scope of this question.

Steve Emmerson
  • 7,702
  • 5
  • 33
  • 59
1

The explanation is straight forward.

fgets(in_buf, BUFF_SIZE, stdin);

That call blocks the thread until it receives a line of input. That is, it does not return until a newline is input or BUFF_SIZE-1 characters are input.

So even though the signal handler sets exit_flag to FALSE, the rcv_usr_input thread will not see that until it unblocks from fgets. Which happens when you pressed "return".

kaylum
  • 13,833
  • 2
  • 22
  • 31
  • thanks for your comment, please see my comment to Mike, and if you could help that would be great :) thanks! – Adiel Feb 20 '16 at 20:57
1

According to http://www.cplusplus.com/reference/cstdio/fgets/, fgets blocks until the specified number of bytes have been read.

I suggest trying fread or some other input reception function that isn't blocking and then read only one byte at a time. Here's sample code to help you:

if (fread(in_buf, 1,1, stdin) > 0){
//character has been read
}

And I wouldn't worry about the extra sleep statement in your signal handler as it causes delays in forceful exiting at best.

Mike -- No longer here
  • 2,064
  • 1
  • 15
  • 37
  • thanks for your comment. the reason i'm using fgets is to cover a scenario where a user enters, for example, 456 chars (fell a sleep on the keyboard for a minute lets say). fread will not cover this scenario i assume, therefore if i read one char only, lets say the user will enter 10 chars, the 9 other chars will be wait to be read from stdin in the next run, no? please correct me if i'm wrong, and also - if i'm right, i'll be happy for a workaround kind of scenario as described Thanks ! – Adiel Feb 20 '16 at 20:56
  • When a character is detected, store it into a buffer and increment the buffer pointer until the desired number of characters are reached for processing. Also, you can implement timers in to your program and run a function when a certain amount of time passes. – Mike -- No longer here Feb 20 '16 at 21:03
  • `fread` will not work as it has almost exactly the same problem as `fgets`. It will still block until it reads input. Instead, a common way to do this is to use a function, like `read`, which aborts when a signal occurs. Then you look at `errno` to see if it is `EINTR`. – kaylum Feb 20 '16 at 21:11
  • @kaylum, i've read the man page for "read" and came across: **If O_NONBLOCK is clear, read() shall block the calling thread until some data becomes available.** That means if i understand correctly, that i DO need to set O_NONBLOCK in order for "read" to not block the thread, right? – Adiel Feb 21 '16 at 06:57
  • @Adiel No, don't set that. You **do** want `read` to block. Because in normal operation you want it to wait for input from the user right? You only want it to stop blocking early if a signal is received. And that is exactly how `read` behaves by default. That is, `read` blocks waiting for input. If there is user input then it returns with success. If there is a signal then it unblocks and returns with error. – kaylum Feb 21 '16 at 07:09
  • @kaylum, i think i've understood - when actually receiving ctrl+c signal, read will return with EINTR and after verifying it i could continue my while loop (which will terminate due to the flag changes) Thanks friend – Adiel Feb 21 '16 at 07:54
  • Yes. Almost. `read` will return -1 on any error (including signal). Check `errno` for the actual error code which will be `EINTR` in the signal case. – kaylum Feb 21 '16 at 07:58
  • @kaylum i'm having trouble with read. according to the man page, read detects a signal an and aborts, **only if count = 0** - the problem is, that when setting count = 0 , a 'true' input is never read from the user (because the instruction is to read up to **count** bytes from file descriptor) Do you have any suggestion how to also be able to receive input from user AND check for errors? Thanks – Adiel Feb 24 '16 at 18:15