Search code examples
cmultithreadingpthreadssignalspthread-join

Trouble with signal catching and thread termination - C


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


Solution

  • 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.