How to properly call destructors on exit

How do I ensure that destructors are called on program exit wether normal termination or with ctrl C, or user directed? I need to properly shutdown a serial port (Biscuit object) and the irobot create (Robot object). I put the shutdown functions in their respective object destructors but they are not being called. I also am terminating the program from another function at times.

Thanks
Chris
Objects with automatic storage duration should die gracefully even in exceptional cases:
http://stackoverflow.com/questions/2331316/what-is-stack-unwinding

The Ctrl-c on the other hand probably does bypass that. What you do need for it is a signal handler that catches the interrupt and makes the program "terminate normally".
http://stackoverflow.com/questions/4217037/catch-ctrl-c-in-c
On a sane system that issues fatal signals (Ctrl-C isn't one), your program's resources will be cleaned by the system when it dies, even though no destructor code runs because your program is crashing horribly.

If your program crashes, no destructors are called. Don't let your program crash and you'll be fine.

If another system is depending on you to e.g., formally close a connection and you're doing it in a destructor, you're in trouble. You can handle some signals, including SIGINT (control-C) and SIGQUIT (control-\).

Be aware that if you invoke std::exit, only objects with automatic storage duration (subject to some restrictions) are not destroyed. This is an important caveat especially if you're doing real work in destructors.
Last edited on
I came up with this solution. Let me know if it is adequate or if its breaking any rules. I found out that there is a std::signal library which is probably an abstraction of signal.h. I dont understand these lines very well :
 
If the user defined function returns when handling SIGFPE, SIGILL, SIGSEGV or any other implementation-defined signal specifying a computational exception, the behavior is undefined. 


I read that the signal handler function will return to the point in the program of its invocation such that if a SIGSEGV was signaled by accessing unallocated memory for example, there is a potential ( if the error was in a do .. while loop ) to loop over the part of the code that caused the SIGSEGV, repeating the error over and over again. This was my initial problem which was solved by the longjmp call which put the next point of execution outside of the do .. while loop.

Q1. Is this what the line above means by "undefined behavior". If so I fixed it with a longjmp. Or should I be expecting some other undesired and unexpected consequences later?

The code below seems to make it "bulletproof" for the signals that I was handling. That is to say, the signal handler is called once only and program cleanup ( closing of the serial port and turning off the robot ) happens only once, regardless of successive signals.

What of concurrent signals? By inserting a delay in the signal handler and pressing Ctrl C, a SIGINT is generated resulting in a second call to the signal handler while the first one hasn't finnished yet. Since I am using parallel threads, quite conceivably, another thread might issue a signal and call the signal handler synchronously.

Q2. How can I block any other signal while I am processing the first? Something like :

1
2
3
4
5
6
7
8
9
int main() {

signal_raised;
block_other_signals; // or ignore since we are terminating anyway
call_signal_function; 

return
}


It seems quite possible also to "skip" past the error with longjmp by setjmp at the bottom of the while loop just before its condition (maintaining the program_running=true) such that we avoid the part of the code that caused the error this time only ( in the case that for example, the error was caused by the user not putting in two words when prompted, causing for example stoi(tokens[1]) to fail. And a separate handler can be called for this specific type of error as well as a setjmp different from all the other signals. ) It would seem that doing this would allow the user to be prompted a second time for correct input. (or a third or a fourth until correct number of words is recieved). The code demonstrates this.


Here is the working code :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <cstdlib>
#include <csignal>
#include <iostream>
#include <atomic>
#include <setjmp.h>
#include <unistd.h>

 
volatile std::sig_atomic_t gSignalStatus;
volatile std::atomic<bool> program_running;

jmp_buf return_to_main, return_to_bottom;
  
void sigIntHandler(int signal)
{
  std::cout << "Turning off robot" <<std::endl;  //this calls a toggle function. A second call will turn on the robot (not desired)
  
  program_running=false;
  usleep(5000000); //Delay so we can send a SIGINT with Ctrl C
  
  std::cout << "Signal was " << signal << std::endl;
  longjmp (return_to_main, 1);
}

void sigsegvhandler(int signal){
  std::cout << "Try again " << std::endl;
  longjmp (return_to_bottom, 1);  
    
}

class Biscuit {
public:
    //It makes sense for the object that opens the serial port to close it on destruction
    Biscuit(): status(1) { std::cout << "Biscuit constructor (opens the serial port)" << std::endl; };
    ~Biscuit() { std::cout << "Biscuit destructor (closes the serial port)" << std::endl ; }
    int status;
    
};



int main(int argc, char** argv) {
 // Install a signal handler
  std::signal(SIGSEGV, &sigsegvhandler);
  std::signal(SIGINT, &sigIntHandler);
  std::signal(SIGABRT, &sigIntHandler); 
  std::signal(SIGFPE, &sigIntHandler); 
  std::signal(SIGILL, &sigIntHandler); 
  std::signal(SIGTERM, &sigIntHandler); 

  Biscuit b;
  int counter=0;
  program_running=true;
  
  if (setjmp(return_to_main)) {
      program_running=false;
      
  } else {    
   do {
       std::cout << "Counter is " << counter << std::endl;
      usleep(1000000);
      if (counter > 5) {
          std::raise(SIGSEGV);
      }
      //program would resume here normally after returning from the signal handler,
      //incrementing counter and possibly running into another signal and call to the signal handler
      //that performs the cleanup procedure a second time
      //the longjmp in signal handler ensures we go back to the point of setjmp
      counter++;
      
      if (counter > 10) {
          std::raise(SIGILL);
      }
      
      if (setjmp(return_to_bottom)){
          std::cout << "Bottom from sigsegv " << std::endl;
      } else
          std::cout << "Normal Bottom " << std::endl;
          
      
   } while (program_running);
  } 
  
    return 0;
}



And here is the output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Biscuit constructor (opens the serial port)
Counter is 0
Normal Bottom 
Counter is 1
Normal Bottom 
Counter is 2
Normal Bottom 
Counter is 3
Normal Bottom 
Counter is 4
Normal Bottom 
Counter is 5
Normal Bottom 
Counter is 6               //SIGSEGV raised here
Try again                    //sigsegv handler running   
Bottom from sigsegv  //sigsegv handler jumps to previous bottom of do .. while
Counter is 6              //it seems that it has forgotton the previous run, counter is still at 6 "turning back the time"
Normal Bottom         //just recovered from a sigsegv as if nothing happened
Counter is 7
Normal Bottom 
Counter is 8
Normal Bottom 
Counter is 9
Normal Bottom 
Counter is 10
Turning off robot
Signal was 4
Biscuit destructor (closes the serial port)



Thanks,
Chris
Last edited on
I think I found the solution using sigfillmask.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <cstdlib>
#include <csignal>
#include <iostream>
#include <atomic>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>
 
struct sigaction sigIntHandler;
struct sigaction sigsegv;

volatile std::atomic<bool> program_running;

jmp_buf return_to_main, return_to_bottom;
  
void signalHandler(int signal)
{
  std::cout << "Turning off robot" <<std::endl;  //this calls a toggle function. A second call will turn on the robot (not desired)
  
  program_running=false;
  usleep(5000000); //Delay so we can send a SIGINT with Ctrl C
  
  std::cout << "Signal was " << signal << std::endl;
  longjmp (return_to_main, 1);
}



class Biscuit {
public:
    //It makes sense for the object that opens the serial port to close it on destruction
    Biscuit(): status(1) { std::cout << "Biscuit constructor (opens the serial port)" << std::endl; };
    ~Biscuit() { std::cout << "Biscuit destructor (closes the serial port)" << std::endl ; }
    int status;
    
};



int main(int argc, char** argv) {

   sigIntHandler.sa_handler = signalHandler;
   sigfillset(&sigIntHandler.sa_mask);
   sigIntHandler.sa_flags = 0;
   
   sigaction(SIGINT, &sigIntHandler, NULL);  
   sigaction(SIGSEGV, &sigIntHandler, NULL);  
   sigaction(SIGILL, &sigIntHandler, NULL);
   sigaction(SIGFPE, &sigIntHandler, NULL); 
   sigaction(SIGABRT, &sigIntHandler, NULL); 
   sigaction(SIGFPE, &sigIntHandler, NULL); 
   sigaction(SIGTERM, &sigIntHandler, NULL);
   
  Biscuit b;
  int counter=0;
  program_running=true;
  
  int *i=NULL;
  
  if (setjmp(return_to_main)) {
      program_running=false;
      
  } else {    
   do {
       std::cout << "Counter is " << counter << std::endl;
       
      usleep(1000000);
      if (counter == 6) {
          std::cout << "Seg fault operation" << std::endl;
          *i = 5;
          
      }
      //program would resume here normally after returning from the signal handler,
      //incrementing counter and possibly running into another signal and call to the signal handler
      //that performs the cleanup procedure a second time
      //the longjmp in signal handler ensures we go back to the point of setjmp
      counter++;

      if (counter > 10) {
          std::raise(SIGILL);
      }
      
   } while (program_running);
  } 
  
    return 0;
}

I will mark this as solved. Feel free to leave comments. Thank you for your time and interest.
Chris
Found an issue with the above code. If for example a SIGSEGV signal is triggered before the do .. while loop is entered , the destructor for Biscuit is not called. The solution is a second longjmp to just after the Biscuit object is created (where program_running was still false). However, running exit(1) at that point does not call the destructor for the Biscuit object. The only way I've found is to use try ... catch. The updated code follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <cstdlib>
#include <csignal>
#include <iostream>
#include <atomic>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>

struct sigaction sigIntHandler;


volatile std::atomic<bool> program_running;

jmp_buf return_to_main,  return_to_start;
  
void signalHandler(int signal)
{
  std::cout << "Turning off robot" <<std::endl;  //this calls a toggle function. A second call will turn on the robot (not desired)
  
  if (!program_running) {
	  longjmp (return_to_start,1);
  }
  
  program_running=false;
  usleep(5000000); //Delay so we can send a SIGINT with Ctrl C
  
  std::cout << "Signal was " << signal << std::endl;
  longjmp (return_to_main, 1);
}



class Biscuit {
public:
    //It makes sense for the object that opens the serial port to close it on destruction
    Biscuit(): status(1) { std::cout << "Biscuit constructor (opens the serial port)" << std::endl; };
    ~Biscuit() { std::cout << "Biscuit destructor (closes the serial port)" << std::endl ; }
    int status;
    
};



int main(int argc, char** argv) {

   sigIntHandler.sa_handler = signalHandler;
   sigfillset(&sigIntHandler.sa_mask);
   sigIntHandler.sa_flags = 0;
   
   sigaction(SIGINT, &sigIntHandler, NULL);  
   sigaction(SIGSEGV, &sigIntHandler, NULL);  
   sigaction(SIGILL, &sigIntHandler, NULL);
   sigaction(SIGFPE, &sigIntHandler, NULL); 
   sigaction(SIGABRT, &sigIntHandler, NULL); 
   sigaction(SIGFPE, &sigIntHandler, NULL); 
   sigaction(SIGTERM, &sigIntHandler, NULL);
   
  Biscuit b;
  
  try {
  if (setjmp(return_to_start)) {
      std::cout << "Jumped to start " <<std::endl;
      throw 1;
     //exit(1);  //does not call Biscuit's destructor
  } 
  
  std::cout << "Simulating segfault before loop" << std::endl; std::raise(SIGSEGV);  //simulate a segfault before program_running is set to true but after Biscuit is created
  
  int counter=0;
  program_running=true;
  
  int *i=NULL;
  
  if (setjmp(return_to_main)) {
      program_running=false;
      std::cout << "Jumped to main" <<std::endl;
      
  } else {    
   do {
       std::cout << "Counter is " << counter << std::endl;
       
      usleep(1000000);
      if (counter == 6) {  //simulate a segfault during the do while loop where program_running is true
          std::cout << "Simulating segfault during loop" << std::endl;
          *i = 5;
          
      }
      //program would resume here normally after returning from the signal handler,
      //incrementing counter and possibly running into another signal and call to the signal handler
      //that performs the cleanup procedure a second time
      //the longjmp to return_to_main in signal handler ensures we go back to that point instead of here
      counter++;

      if (counter > 10) {
          std::raise(SIGILL);
      }
      
   } while (program_running);
  } 
  
 }
catch(...) {
	std::cout << "Exception caught " << std::endl;
	
} 
  
    return 0;
}

Last edited on
Topic archived. No new replies allowed.