Heathcliff
Heathcliff

Reputation: 3138

Why does my signal handler only execute once?

I'm playing with signal handling in UNIX and C++ and came across with this issue. I'm trying to write a program that counts to 10, one number per second, and when the user tries to interrupt it with a SIGINT (like CTRL+C) it prints a message telling it it will continue to count no matter what.

So far, I got this:

#include <iostream>
#include <signal.h>
#include <zconf.h>

using namespace std;

sig_atomic_t they_want_to_interrupt = 0;
void sigint_handler(int signum) {
    assert(signum == SIGINT);
    they_want_to_interrupt = 1;
}

void register_handler() {
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGINT);
    sa.sa_handler = sigint_handler;
    sigaction(SIGINT, &sa, 0);
}

int main() {
    register_handler();
    cout << "Hi! We'll count to a hundred no matter what" << endl;
    for (int i = 1; i <= 100; i++) {
        if (they_want_to_interrupt == 1) {
            cout << endl << "DON'T INTERRUPT ME WHILE I'M COUNTING! I'll count ALL THE WAY THROUGH!!!" << endl;
            they_want_to_interrupt = 0;
        }
        cout << i << " " << flush;
        sleep(1);
    }
    cout << "Done!" << endl;
    return 0;
}

Now, the first time around I send the interrupt signal it works properly:

Hi! We'll count to a hundred no matter what
1 2 ^C
DON'T INTERRUPT ME WHILE I'M COUNTING! I'll count ALL THE WAY THROUGH!!!
3 4

But if I send a second interrupt signal, the process is stopped.

Why does it happen? I tried reading the manual on ´sigaction´ to try to see if there's something that will make the handler I created not be popped when the signal is caught and roll back to SIG_DFL, but couldn't work it out.

Thanks

Upvotes: 2

Views: 1198

Answers (2)

M.M
M.M

Reputation: 141554

In this code:

struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT);
sa.sa_handler = sigint_handler;
sigaction(SIGINT, &sa, 0);

the sa.sa_flags field (and others) are uninitialized which may cause unexpected results. It would be better to zero-initialize the struct at the start, e.g.:

struct sigaction sa = { 0 };

Also, the sig_atomic_t flag should be declared as volatile to prevent the optimizer introducing unexpected behaviour.

Upvotes: 1

Matthew Fisher
Matthew Fisher

Reputation: 2336

You can just reset the signal handler each time a signal is sent. I've seen this for handling SIGUSR when a signal might be expected repeatedly.

#include <iostream>
#include <cassert>
#include <signal.h>
#include <zconf.h>

using namespace std;

void register_handler();
sig_atomic_t they_want_to_interrupt = 0;
void sigint_handler(int signum) {
    assert(signum == SIGINT);
    they_want_to_interrupt = 1;
    register_handler();
}

void register_handler() {
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGINT);
    sa.sa_handler = sigint_handler;
    sigaction(SIGINT, &sa, 0);
}

int main() {
    register_handler();
    cout << "Hi! We'll count to a hundred no matter what" << endl;
    for (int i = 1; i <= 100; i++) {
        if (they_want_to_interrupt == 1) {
            cout << endl << "DON'T INTERRUPT ME WHILE I'M COUNTING! I'll count ALL THE WAY THROUGH!!!" << endl;
            they_want_to_interrupt = 0;
        }
        cout << i << " " << flush;
        sleep(1);
    }
    cout << "Done!" << endl;
    return 0;
}

Upvotes: 2

Related Questions