Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I interrupt a background thread that's blocking on IO?

I have a C program with two threads: the main thread continously reads data from the network and prints it out to the screen, while the secondary thread listens for and handles keypresses from standard input.

Currently my program catches SIGINT, SIGTERM, and SIGPIPE in order to terminate the program cleanly. My problem is that at the end of the main thread (once the main loop has terminated from the signal handler), it attempts to revert the terminal settings using tcsetattr, however this blocks until the current fgetc call on the other thread returns.

How can I interrupt the background thread so that the fgetc call returns and the main thread can restore the terminal settings and exit cleanly?

I have tried using pthread_kill(thread, SIGINT) but that just causes my existing signal handler to be called again.

Relevant code:

// If the program should still be running.
static sig_atomic_t running = 1;

// Background thread that reads keypresses.
pthread_t thread;

static void *get_keypresses();

static void receive_signal(int signal) {
    (void)signal;
    running = 0;
}

int main(int argc, char *argv[]) {
    // Set up signal handling.
    if(signal(SIGINT, receive_signal) == SIG_ERR) {
        fprintf(stderr, "Error setting signal handler for SIGINT.\n");
    }
    if(signal(SIGTERM, receive_signal) == SIG_ERR) {
        fprintf(stderr, "Error setting signal handler for SIGTERM.\n");
    }
    if(signal(SIGPIPE, receive_signal) == SIG_ERR) {
        fprintf(stderr, "Error setting signal handler for SIGPIPE.\n");
    }

    // Set up thread attributes.
    pthread_attr_t thread_attrs;
    if(pthread_attr_init(&thread_attrs) != 0) {
        perror("Unable to create thread attributes");
        exit(2);
    }
    if(pthread_attr_setdetachstate(&thread_attrs, PTHREAD_CREATE_DETACHED) != 0) {
        perror("Unable to set thread attributes");
        exit(2);
    }

    // Set up terminal for reading keypresses.
    struct termios orig_term_attr;
    struct termios new_term_attr;
    tcgetattr(fileno(stdin), &orig_term_attr);
    memcpy(&new_term_attr, &orig_term_attr, sizeof(struct termios));
    new_term_attr.c_lflag &= ~(ECHO|ICANON);
    tcsetattr(fileno(stdin), TCSANOW, &new_term_attr);

    // Start background thread to read keypresses.
    if((pthread_create(&thread, &thread_attrs, &get_keypresses, NULL)) != 0) {
        perror("Unable to create thread");
        exit(2);
    }

    // Main loop.
    while(running) {
        // Read data from network and output to screen.
    }

    // Restore original terminal attributes. ***IT BLOCKS HERE***
    tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr);

    return 0;
}

// Get input from the keyboard.
static void *get_keypresses() {
    int c;
    while(running) {
        // Get keypress. ***NEEDS TO BE INTERRUPTED HERE***
        if((c = fgetc(stdin)) != - 1) {
            // Handle keypress.
        }
    }
    return NULL;
}
like image 896
DanielGibbs Avatar asked Dec 21 '25 05:12

DanielGibbs


2 Answers

There are two ways to go:

  • you change your code not to block (using O_NONBLOCK, poll(), select(), etc.),
  • you force your blocked code to get kicked out of the blocking system call.

Forcing the end of a system call can basically be done in two ways: either you make the system call impossible to finish (for example, you close the file descriptor the blocking call waits on), or you send some signal to that thread, and make sure that the signal handling is properly set up. The proper signal handling means that the signal is not ignored, not masked, and that the signal flags are set up in such a way that the system call does not get restarted after the signal handling (see sigaction(), SA_RESTART flag).

like image 67
Laszlo Valko Avatar answered Dec 23 '25 20:12

Laszlo Valko


Replace

if((c = fgetc(stdin)) != -1) 

with

if (0 < read(fileno(stdin), &c, 1)) 

read() would be interupted if the thread received a signal.

like image 22
alk Avatar answered Dec 23 '25 21:12

alk



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!