6

In both C and C++, atexit functions are called either inside exit, or after main returns (which notionally calls exit: __libc_start_main(argc,argv) { __libc_constructors(); exit(main(argc,argv)); }).

Is there a way to find out if we're inside the exit sequence? Destructors of C++ global and local statics are registered with atexit, so your code can certainly be called into at this stage. (Interestingly, on some platforms if you try to create a C++ local-static object inside exit, it deadlocks on the exit lock!)

My best attempt so far is as follows:

static bool mainExited = false;
static void watchMain() {
  static struct MainWatcher {
    ~MainWatcher() { mainExited = true; }
  } watcher;
}

When you want to watch for exit, you call watchMain(), and mainExited tells you at any time whether or not the exit sequence has begun -- except of course if a later-initialized local-static object is destructing!

Can the technique be improved to correct this, or is there another method that would work?

Aside - the use case!

While the problem is interesting from a language point-of-view (a bit like "can I tell if I'm inside a catch block?"), it's also useful to outline a use-case. I came across the problem while writing some code which will be run with and without a JVM loaded (with either direct calls or calls via JNI). After the JVM exits, the C atexit handlers are called, and JNI_OnUnload is not called if the JNI shared library is not unloaded by the class loader.

Since the shared library's objects can be destructed both by explicit destruction (and should free their resources), and by cleanup at exit, I need to distinguish these two cases safely, since the JVM is gone by the time we reach the exit code! Basically without a bit of sniffing there's no way I can find in the JNI specs/docs for a shared library to know whether the JVM is still there or not, and if it's gone, then it's certainly wrong to try and free up references we have to Java objects.

Nicholas Wilson
  • 9,435
  • 1
  • 41
  • 80

2 Answers2

7

The real issue here is that the ownership semantics you've listed are messed up. The JVM kinda owns your shared library but also kinda doesn't. You have a bunch of references to Java objects that sometimes you need to clean up but sometimes you don't.

The real solution here is simply to not keep references to Java objects as global variables. Then you won't need to know if the JVM still exists or not when the library is unloaded for whatever reason. Just keep references to Java objects from inside objects referenced by Java and then let the JVM care about whether or not it needs to free them.

In other words, don't make yourself responsible for cleanup on exit in the first place.

ChrisF
  • 134,786
  • 31
  • 255
  • 325
Puppy
  • 144,682
  • 38
  • 256
  • 465
1

Your watcher doesn't need to rely on any static initialization order:

#include <iostream>

struct MainWatcher  // : boost::noncopyable
{
    enum MainStatus { before, during, after };

    MainWatcher(MainStatus &b): flag(b) { flag = during; }
    ~MainWatcher() { flag = after; }
    MainStatus &flag;
};

//////////////////////////////////////////////////////////////////////
// Test suite
//////////////////////////////////////////////////////////////////////

// note: static data area is zero-initialized before static objects constructed
MainWatcher::MainStatus main_flag;

char const *main_word()
{
    switch(main_flag)
    {
        case MainWatcher::before: return "before main()";
        case MainWatcher::during: return "during main()";
        case MainWatcher::after: return "after main()";
        default: return "(error)";
    }
}

struct Test
{
    Test()  { std::cout << "Test created "   << main_word() << "\n"; }
    ~Test() { std::cout << "Test destroyed " << main_word() << "\n"; }
};

Test t1;

int main()
{
    MainWatcher watcher(main_flag);

    // rest of code
    Test t2;
}
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Nice try, but what about if `exit` is called? You're right though that, if you can modify main, then it's possible to detect main's exit before all destructors of statics are called. – Nicholas Wilson Mar 16 '15 at 09:12
  • If the `exit` function is called then the program is considered to terminate without `main` returning. Your title could be read in two ways; did you mean "How do you know if main has returned?", or "How do you know if an `atexit` handler has been executed?" (or something else again - there are ways to terminate the program without running atexit handlers?) – M.M Mar 16 '15 at 13:02
  • Note that in general, static destructors are not required to use the `atexit` mechanism – M.M Mar 16 '15 at 13:10
  • There do seem to be [some issues](http://stackoverflow.com/questions/3810157/dlclose-does-not-call-the-destructor-of-global-objects) surrounding gcc, and destructors of static objects in a shared library. – M.M Mar 16 '15 at 13:17
  • For all the six Unix platforms I have to support, static destructors are supported fine by libc -- the poster in that question is using an unspecified platform and probably doing something weird. On linux and OS X certainly, there are no issues with destructors of static objects. I'm sorry the title is a bit ambiguous; the first paragraphs state the problem clearly though: "Is there a way to find out if we're inside the exit sequence?" – Nicholas Wilson Mar 16 '15 at 13:46
  • @NicholasWilson define "the exit sequence"? – M.M Mar 16 '15 at 13:50