9

Possible Duplicate:
Is destructor called if SIGINT or SIGSTP issued?

My code like this:

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

void handler(int) {
    std::cout << "will exit..." << std::endl;
    exit(0);
}

class A {
public:
    A() {std::cout << "constructor" << std::endl;}
    ~A() {std::cout << "destructor" << std::endl;}
};

int main(void) {
    signal(SIGINT, &handler);

    A a;
    for (;;);

    return 0;
}

When I pressed Ctrl-C, it printed:

constructor
^Cwill exit...

There is no "destructor" printed. So, how can I exit cleanly?

Community
  • 1
  • 1
superK
  • 3,932
  • 6
  • 30
  • 54
  • 1
    Note that the "correct" answer is not as "trivial" as some responses below suggest. For example, you should consider using `sigaction` intead of `signal`. Also, the main reason for not using `exit()` in an async signal handler is that it is simply [not supported](http://pubs.opengroup.org/onlinepubs/009604599/functions/xsh_chap02_04.html#tag_02_04). Likewise for using `iostreams` in the signal handler (I'm not 100% sure about that though). Then, using a plain `bool` instead of `volatile std::sig_atomic_t` may not be reliable or cause undefined behavior. – Christian.K Mar 08 '12 at 10:13
  • http://ppst.me/5cUMeA wrote this little hack for you, I find this much cleaner than using a global variable to keep track of the execution flow. – Filip Roséen - refp Mar 08 '12 at 10:26
  • 1
    @refp Problem is, it exhibits undefined behavior all over. – Christian.K Mar 08 '12 at 10:50
  • @Christian.K current implementation, yes. if you move the call to `signal` to after the calls to `handler` (with two arguments) it's **not** undefined behavior. I was a bit quick when writing it, and thought of the above myself but had a meeting to attend. – Filip Roséen - refp Mar 08 '12 at 10:53
  • If you can afford to go platform specific, have a look at gccs -fnon-call-exceptions – PlasmaHH Mar 08 '12 at 10:53
  • @refp It still is, as long as you call `std::exit()` from the handler at least. – Christian.K Mar 08 '12 at 11:00
  • @Christian.K true though, putting the `std::vector` on the heap (store address in a static pointer inside `handler`) and freeing it manually would solve that issue though. But making sure there is absolutely no `UB` when using handlers is hard, since it requires you pretty much not to use any static variables - at all, and you'll need *mutexes* to protect the static variables you do have. – Filip Roséen - refp Mar 08 '12 at 11:08
  • @refp Yes, but this is the whole point of that question/answer thread is it? You can't do pretty much anything in a signal handler then setting a flag (and even that has to be correct) ;-) – Christian.K Mar 08 '12 at 11:16

5 Answers5

14

With difficulty. Already, the code you've written has undefined behavior; you're not allowed to output to a stream in a signal handler; for that matter, you're not allowed to call exit either. (I'm basing my assertions here on the Posix standard. In pure C++, all you're allowed to do is assign to a variable of sig_atomic_t type.)

In a simple case like your code, you could do something like:

sig_atomic_t stopFlag = 0;

void
handler( int )
{
    stopFlag = 1;
}

int
main()
{
    signal( SIGINT, &handler );
    A a;
    while ( stopFlag == 0 ) {
    }
    std::cout << "will exit..." << std::endl;
    return 0;
}

Depending on the application, you may be able to do something like this, checking the stopFlag at appropriate places. But generally, if you try this, there will be race conditions: you check stopFlag before starting an interuptable system call, then do the call; the signal arrives between the check and the call, you do the call, and it isn't interrupted. (I've used this technique, but in an application where the only interruptable system call was a socket read with a very short timeout.)

Typically, at least under Posix, you'll end up having to create a signal handling thread; this can then be used to cleanly shut down all of the other threads. Basically, you start by setting the signal mask to block all signals, then in the signal handling thread, once started, set it to accept the signals you're interested in and call sigwait(). This implies, however, that you do all of the usual actions necessary for a clean shutdown of the threads: the signal handling thread has to know about all other threads, call pthread_cancel on them, etc., and you're compiler has to generate the correct code to handle pthread_cancel, or you need to develop some other means of ensuring that all threads are correctly notified. (One would hope, today, that all compilers handle pthread_cancel correctly. But one never knows; doing so has significant runtime cost, and is not usually needed.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Thanks in particular for the final paragraph. I've been doing that for a while, and it's nice to see more evidence that other people agree it's a good thing to do. – Lightness Races in Orbit Mar 08 '12 at 10:52
  • I don't get your point about race conditions. The question only wants to use the interrupt to stop the program; the interrupt handler can set the `stopFlag` and nobody every clears it. You can test the flag at your convenience anywhere (which is outside any system calls of course). Your interrupt may than happen during, or just before, a system call but the handler just sets the flag; the system call will complete and at the next test occasion the flag is found set and shut down starts. You may loose a fraction of a µs, but nothing serious. The main problem is ensuring regular tests are done. – Marc van Leeuwen Apr 20 '15 at 14:21
  • Maybe race conditions isn't the correct word for this scenario, but the problem is clear: a thread may be in a long blocking system call (read on a socket or pipe to which no one writes, for example). A signal will interrupt this, but there is a window between when the flag is tested, and when the system call occurs, in which the thread can block. – James Kanze May 01 '15 at 08:30
  • 1
    `stopFlag` should be `volatile`. – a3f Jul 26 '16 at 11:36
  • How about using `setjmp/longjmp` to jump from the signal handler back to the main loop, then exit "cleanly" from there? Is this doable safely and under which conditions? – max Jun 29 '17 at 16:38
2

Memory should be freed anyway. but if you've got code to be handled, I guess you'd have to track all your objects and then destroy them as needed (e.g. having the constructor adding them to a std::set, while the destructor removes them again). However this wouldn't ensure proper order of destruction (which might require some more complex solution).

You could as well use your signal handler to set some flag that will leave the infinite loop (or whatever you're doing in your main loop) instead of simply terminating using exit().

Mario
  • 35,726
  • 5
  • 62
  • 78
  • 2
    With regards to the first solution: you can't access a global `std::set` in a signal handler without incurring undefined behavior. (You can't do much of anything in a signal handler without incurring undefined behavior. His current code has undefined behavior.) – James Kanze Mar 08 '12 at 10:29
  • Hm, yeah, you're right. So would in any case require some flag to be set only to leave the loop and/or cause additional handling. – Mario Mar 08 '12 at 11:11
  • About all you can legally do in a signal handler is set a flag or effectively abort the program, without flushing IO buffers, etc. (`_exit()` or `abort()`). So unless you want to abort, you have to set a flag. Or handle the signals in a separate thread (at least under Unix). – James Kanze Mar 08 '12 at 11:46
  • Yeah, I remember interupt handling in Assembler back at uni, tnx. :) – Mario Mar 08 '12 at 11:56
2

You need to exit from the main function's scope to have the destructor working:

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

bool stop = false;
void handler(int) {
    std::cout << "will exit..." << std::endl;
    stop = true;
}

class A {
public:
    A() {std::cout << "constructor" << std::endl;}
    ~A() {std::cout << "destructor" << std::endl;}
};

int main(void) {
  A a;
  signal(SIGINT, &handler);

  for (;!stop;);

  return 0;
}
perreal
  • 94,503
  • 21
  • 155
  • 181
2

It's because the context of the normal code and the signal handler is different. If you put the variable a in global scope (i.e. outside of any function) you will see that the destructor is called properly.

If you want to handle cleaning up yourself (instead of letting the run-time and OS handle it), you can have a conditional loop, something like this:

bool keep_running = true;

void handler(int) {
    std::cout << "will exit..." << std::endl;
    keep_running = false;
}

int main(void) {
    signal(SIGINT, &handler);

    A a;
    while (keep_running);

    return 0;
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 1
    Or you might. Calling `exit` from a signal handler is undefined behavior, at least according to C90 and Posix, so anything might (and in practice does) happen. – James Kanze Mar 08 '12 at 10:27
1

exit terminates the process almost immediately; in particular, objects with automatic storage duration are not destroyed. Streams are also flushed and closed, but you're not allowed to touch streams from inside a signal handler. So...

Simply don't call exit from a signal handler; set some atomic flag to instruct the loop to end instead.

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

sig_atomic_t exitRequested = 0;

void handler(int) {
    std::cout << "will exit..." << std::endl;
    exitRequested = 1;
}

struct A {
     A() { std::cout << "constructor" << std::endl; }
    ~A() { std::cout << "destructor" << std::endl; }
};

int main() {
    signal(SIGINT, &handler);

    A a;
    for (; !exitRequested; );
}
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Nitpicker-Alert :-) Well, technically `exit` [does not](http://www.cplusplus.com/reference/clibrary/cstdlib/exit/) exit the process immediately - depending on your definition of "immediate" of course. Especially for C++ it does run the destructors for static objects (sorry I have no ref. to the standard for that). All of which is contributing to the reason why `exit` is not considered async-signal-safe and only `_exit()` is, which does nothing of the before mentioned. – Christian.K Mar 08 '12 at 10:28
  • 1
    @Christian.K It's not nitpicking. There are very few things you can legally do in a signal handler, because of its asynchronous nature. Under Posix, for example, you can do a system level `write`, but not anything with `FILE*` or an iostream. And `exit()` is forbidden, because it will do things with `FILE*` and `iostream` (flush any output, for example). – James Kanze Mar 08 '12 at 10:38
  • @JamesKanze Oh yes, absolutely. Hence, my comment to the original question :-) I used the term "nitpicking", because the answer abolished the use of `exit` anyway (if for other reasons). – Christian.K Mar 08 '12 at 10:48
  • 1
    Ah thanks :-) there is even a [related question](http://stackoverflow.com/a/4769269/21567) on SO already - go figure :-) – Christian.K Mar 08 '12 at 10:54
  • @Christian.K: That's about `_Exit`, not `exit`, though. – Lightness Races in Orbit Mar 08 '12 at 10:55
  • The particular answer, yes (should have paid more attention, sorry). The question seems to be about both. Anyway, it talks about the conceptual differences between `exit()` with destructors being called and the "other". – Christian.K Mar 08 '12 at 10:59
  • @Christian.K: I don't see anything in that question about `exit()` whatsoever – Lightness Races in Orbit Mar 08 '12 at 11:20
  • Well there is `exit` mentioned [here](http://stackoverflow.com/questions/4769229/how-will-exit-behave-in-a-c-program/4769269#comment5659812_4769269) and `_exit` mentioned in the question. But I guess you're right. It does not serve as a very good example. – Christian.K Mar 08 '12 at 11:32
  • @Christian.K: `exit` is not the same as `_exit`, and I don't think one passing comment counts as question relevance ;) – Lightness Races in Orbit Mar 08 '12 at 11:39
  • This'll be the last one from me, because SO is already complaining about using chat instead, but: that `exit` is not the same as `_exit` was my initial point, wasn't it (I'm getting confused, never mind ;-). But I agree on the "question relevance" point you make. – Christian.K Mar 08 '12 at 11:41