Wednesday, January 19, 2011

Autocleanup using the destructor Vs atexit and signal

Performing autocleanup using the destructor of a locally defined variable like a std::auto_ptr is a neat trick. However it has some serious drawbacks when considered against signals and functions such as std::exit. Many programmers don't realize that auto_ptrs DO NOT operate correctly when exit() is called.

Here is a table summarizing which function operates(O) and which doesnt(X) and in a certain situation.

Destructors
Auto Pointers
et al
atexit() signal()
Real signals X X O
Raised signals
(without signal handler)
X X X
Raised signals
(with signal handler)
O O O
Soft exceptions(divide by zero) O O X
throws O O X
Exit() et al O X X

Notes:
  • There is are several important differences between Raised signals and Real signals.
    • Real signals WILL forced the exit of the program.
    • Raised signals can continue if a signal handler is installed, and will allow for correct stack unwind after the fact. This can lead to a real sigv if you didn't ensure the exit but cleaned up the critical memory anyway.
  • When functions such as exit() and terminate() are called the stack is NOT unwound and objects within the local scope are NOT destroyed before the program terminates.
  • The atexit function is not called when the program dies from a crash. A signal handler is required.

My advice is to use a singleton class for any global controllers (like a database access handler that needs to open and close the database). This singleton MUST NULL OUT ITS SELF POINTERS and check for the NULL on both its initialize and destroy routines as it may be called multiple times, depending on how the program/threads are coming down/up.

Here is some code to test it out for yourself.
#include <csignal>
#include <iostream>
#include <stdlib.h>


using namespace std;

void bye_exit(void)
{
    cout << "AtExit Closed!" << endl;
}

void bye_sig(int param)
{
    cout << "Signal Closed!" << endl;
    //std::exit();  //<<-- this really needs to be here!
}

int main(int argc, char const * const *argv)
{
    try
    {
        if (atexit(bye_exit) != 0) 
        {
            cerr << "atexit implosion" << endl;
            return EXIT_FAILURE;
        }

        if (signal (SIGTERM,bye_sig)==SIG_ERR) 
        {
            cerr << "signal implosion" << endl;
            return EXIT_FAILURE;    
        }

        //uncomment to see the effects of signal on raise by runing mode 4
        //if (signal (SIGSEGV,bye_sig)==SIG_ERR) 
        //{
        //    cerr << "signal implosion" << endl;
        //    return EXIT_FAILURE;    
        //}
        
        struct AutoStuff
        {
            AutoStuff()
            {
                cout << "AutoStuff Open!" << endl;
            }

            ~AutoStuff()
            {
                cout << "AutoStuff Closed!" << endl;
            }
        } automatic;

        
        int trial = -1;
        if(argc == 2) trial = atoi(argv[1]);

        cout << "Trial: " << trial << endl;
    
        switch (trial)
        {
            case 0:
                std::exit(EXIT_SUCCESS);
            case 1:
                {
                    float  b = 0;
                    float  a = 1/b;
                }
            case 2:
                {
                    throw "ahhh!";
                }
            case 3:
                {
                    raise(SIGTERM);
                    cout << "Wow! Its still alive!" << endl;
                }                
            case 4:
                {
                    raise(SIGSEGV);
                    cout << "Wow! Its still alive!" << endl;
                }                
            case 5:
                return EXIT_SUCCESS;
        }

    }
    catch (exception const &e)
    {
        cout << "Unexpected exception: " <<  e.what() << endl;
    }
    catch (...)
    {
        cout << "Unexpected exception" << endl;
    }
    return  EXIT_FAILURE;
}

No comments:

Post a Comment