Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you know whether main has exited?

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.

like image 407
Nicholas Wilson Avatar asked Mar 12 '15 09:03

Nicholas Wilson


2 Answers

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.

like image 111
Puppy Avatar answered Oct 17 '22 16:10

Puppy


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;
}
like image 37
M.M Avatar answered Oct 17 '22 16:10

M.M