This is a summary of our discussion on initialization and finalization in the UPC++ meeting on 5/6/15 and is a fork of issue #3.
1. Implicit calls to upcxx::init() and upcxx:finalize()
We'd like remove the requirement that the user explicitly call init() and finalize() in main(). However, we cannot do so until GASNet allows us to initialize it without passing it &argc and &argv. Once this restriction is removed, we could implicitly call UPC++ init() and finalize() by declaring a static object that calls init() in its constructor and finalize() in its destructor. However, C++ provides us no way to guarantee that this constructor is the first to run and that the destructor is the last to run. (There is a std::atexit() function to queue things to run on program termination, but there is no way to guarantee that they run after all destructors.)
2. Initialization semantics
Regardless of whether init() is called implicitly or explicitly, we need to define what operations are legal before initialization. (If initialization is implicit, then we would specify that initialization happens at some arbitrary point before main, and what operations are legal before entering main.) So far, the operations we think are important are declaring shared variables and arrays and being able to call query the number of ranks and rank ID.
a) Shared variables and arrays
Currently, we queue initialization of shared variables and actually initialize them once init() is called. For shared arrays, we require user to explicitly initialize them at some later point, but we think we can queue their initialization as well.
b) Ranks and rank IDs
We don't want to have to check for initialization when calling myrank() or ranks(). (Currently, the functions aren't implemented efficiently, but they can be implemented as just reads.) Instead, we can provide special functions to be used before main that check for and perform initialization, but tell users to otherwise use myrank() and ranks() for performance Suggestions on functions names are welcome. (myrank_preinit() and ranks_preinit()?)
c) Arbitrary code
Finally, we can provide the ability for the user to queue arbitrary code to run upon initialization, using a call such as upcxx::atinit(). We would specify that functions registered using atinit() would run sequentially in the order they are registered once UPC++ is initialized (which may be at some arbitrary point before main if initialization is implicit). If UPC++ is already initialized when atinit() is called, then the given function would run immediately.
3. upcxx::is_init() and multiple calls to upcxx::init()/finalize()
For composability, Dan suggested in issue #3 either introducing an is_init() or reference counting calls to init()/finalize(). I actually think both should be provided. is_init() is useful to determine what UPC++ operations are legal to use as defined by UPC++ initialization semantics. Allowing multiple calls to init()/finalize() would allow us to introduce implicit initialization and finalization without breaking existing code.
4. Finalization/exit semantics
Currently, calling upcxx::finalize() (whether explicitly now or implicitly in the future) results in program termination without performing the cleanup steps required by C++. In particular, C++ specifies that destructors be called on thread-local and static objects, functions registered by calling std::atexit() be called, C streams be flushed and closed, and files created with std::tmpfile be removed (see here for more details). None of these steps are done by UPC++. Moving to implicit finalization would result in an unpredictable subset of destructors and functions registered through atexit() being run before exit.
We have not been able to come up with a solution to this issue. Ideally, on normal termination, we'd call a barrier after C++ cleanup completes and then call GASNet exit. (The barrier is necessary to ensure that cleanup has completed on all ranks, since GASNet exit will cause all ranks to exit.) However, there don't appear to be any standard C++ hooks for running code after cleanup. Even if there were, we'd need some way of distinguishing between collective and non-collective termination, since calling a barrier in the latter case could lead to deadlock. However, this should be doable.
It seems like at the moment, we have to specify that the standard C++ cleanup process is not guaranteed to run in UPC++, which is unfortunate. Adding the ability to queue functions with upcxx::atexit() might help mitigate this, but it's not clear we can guarantee that such functions run in the case of non-collective termination.
5. Non-collective termination
UPC++ should provide a upcxx::exit() call for non-collective termination of a program. However, the issues with finalization occur here as well, and in fact may be worse since we wouldn't be able to use a barrier to ensure cleanup completion.
Re: Finalization/exit semantics
Should this be fixed in GASNet? Why GASNet has to kill any process in a normal shutdown? This seems to cause troubles for any C/C++ programs that require some cleanup during a normal exit.