views:

354

answers:

6

I have a destructor that performs some necessary cleanup (it kills processes). It needs to run even when SIGINT is sent to the program. My code currently looks like:

typedef boost::shared_ptr<PidManager> PidManagerPtr
void PidManager::handler(int sig)
{
  std::cout << "Caught SIGINT\n";
  instance_.~PidManagerPtr();  //PidManager is a singleton
  exit(1);
}
//handler registered in the PidManager constructor

This works, but there seem to be numerous warnings against explicitly calling a destructor. Is this the right thing to do in this situation, or is there a "more correct" way to do it?

A: 

One other way could be to have the singleton dynamically allocated (on first use or in main), and delete it for cleanup.

Eh. I guess your PidManagerPtr actually points to a dynamically allocated object ... But doesn't boost::shared_ptr actually clean up on reallocation? So it should be enough to:

instance_ = 0;

?

Rasmus Kaj
Well, instance_ = 0 doesn't compile. instance_.reset((PidManager*)NULL) doesn't work either.
Dan Hook
Wait, wait you can't do that. Signal handler can run absolutely anytime. Not the best oportunity to manipulate the heap.
EFraim
+2  A: 

Never explicitly call destructor unless object was constructed with placement new. Move cleanup code into separate function and call it instead. The same function is to be called from the destructor.

BostonLogan
Obviously it's bad to explicitly call a destructor in a situation where the destructor could be called again, but it seems like it's pretty safe if I'm exiting in the very next line. What bad stuff could happen that I should never do this?
Dan Hook
@Dan Hook: Keep in mind there is very little you know about thread's state in signal handling. The object state can be completely incoherent - it might be inside this object's method.
EFraim
Calling destructor explicitly is rarely a good idea. It might be safe (in this particular case) and may even be justified (if you want to reuse memory with placement new right after you destroy the object) but do not forget about future programmer who will have to maintain your code. What we are seeing here is an example of "accident of context", i.e. code is bad but it works because program exits the very next line. "Programming by coincidence" is a bad habit, do not cultivate it.
BostonLogan
A: 

Just call reset() on the shared_ptr and it'll remove your instance for you.

Jeff Foster
Are you sure? See http://stackoverflow.com/questions/156373/using-reset-to-free-a-boostsharedptr-with-sole-ownership
Jeff Foster
@Dan: .reset() is only no-op for NULL pointer.
EFraim
+5  A: 

If that object is a singleton, you don't need to use a shared-pointer. (There's only one!)

If you switch it to auto_ptr you can call release() on it. Or perhaps scoped_ptr, calling reset().

This all said, I'm 99% certain that exit() will destruct statically constructed objects. (Which singletons tend to be.) What I do know is that exit() calls the registered atexit() functions.

If your singleton is not destructed automatically by exit, the proper thing to do in your case is to make an atexit hook:

void release_singleton(void)
{
    //instance_.release();
    instance_.reset();
}

// in main, probably
atexit(release_singleton);
GMan
Multiple classes could have their own shared_ptrs to the PidManager instance. Of course, if that happened, my code would just decrement the reference count by one and it wouldn't work. It's a coincidence that only one class actually owns a PidManager at this point in development. I'll have to fix that. Thanks. I'll try out atexit() on Monday.
Dan Hook
Special note about atexit():"Functions registered using atexit() are not called if a process terminates abnormally because of the delivery of a signal."Perhaps I misunderstood the answer, but registering an atexit() handler does not obviate the need for a signal handler.
Dan Hook
A: 

You really don't want to do much of anything in a signal handler. The safest the thing to do is just set a flag (e.g. a global volatile bool), and then have your program's regular event loop check that flag every so often, and if it has become true, call the cleanup/shutdown routine from there.

Because the signal handler runs asynchronously with the rest of the application, doing much more than that from inside the signal handler is unsafe -- whatever data you might want to interact with might be in an inconsistent state. (and you're not allowed to use mutexes or other synchronization from a signal handler, either -- signals are pretty evil that way)

However, if you don't like the idea of having to poll a boolean all the time, one other thing you can do from within a signal handler (at least on most OS's) is send a byte on a socket. So you could set up a socketpair() in advance, and have your normal event loop select() (or whatever) on the other end of the socket pair; when it receives a byte on that socket, it knows your signal handler must have sent that byte, and therefore it's time to clean up.

Jeremy Friesner
A: 

Turns out that doing this was a very bad idea. The amount of weird stuff going on is tremendous.

What was happening

The shared_ptr had a use_count of two going into the handler. One reference was in PidManager itself, the other was in the client of PidManager. Calling the destructor of the shared_ptr (~PidManager() ) reduced the use_count by one. Then, as GMan hinted at, when exit() was called, the destructor for the statically initialized PidManagerPtr instance_ was called, reducing the use_count to 0 and causing the PidManager destructor to be called. Obviously, if PidManager had more than one client, the use_count would not have dropped to 0, and this wouldn't have worked at all.

This also gives some hints as to why calling instance_.reset() didn't work. The call does indeed reduce the reference count by 1. But the remaining reference is the shared_ptr in the client of PidManager. That shared_ptr is an automatic variable, so its destructor is not called at exit(). The instance_ destructor is called, but since it was reset(), it no longer points to the PidManager instance.

The Solution

I completely abandoned the use of shared_ptrs and decided to go with the Meyers Singleton instead. Now my code looks like this:

void handler(int sig)
{
     exit(1);
}

typedef PidManager * PidManagerPtr
PidManagerPtr PidManager::instance()
{
    static PidManager instance_;
    static bool handler_registered = false;
    if(!handler_registered)
    {
        signal(SIGINT,handler);
        handler_registered = true;
    }
    return &instance_;
 }

Explicitly calling exit allows the destructor of the statically initialized PidManager instance_ to run, so no other clean up code need be placed in the handler. This neatly avoids any issues with the handler being called while PidManager is in an inconsistent state.

Dan Hook