How can I handle exit() calls in 3rd party library code?
Asked Answered
C

5

16

I'm working on a C++ application which uses a library written in C by another team. The writers of the library like to call exit() when errors happen, which ends the program immediately without calling the destructors of objects on the stack in the C++ application. The application sets up some system resources which don't automatically get reclaimed by the operating system after the process ends (shared memory regions, interprocess mutexes, etc), so this is a problem.

I have complete source code for both the app and the library, but the library is very well-established and has no unit tests, so changing it would be a big deal. Is there a way to "hook" the calls to exit() so I can implement graceful shutdown for my app?

One possibility I'm considering is making one big class which is the application - meaning all cleanup would happen either in its destructor or in the destructor of one of its members - then allocating one of these big objects on the heap in main(), setting a global pointer to point to it, and using atexit() to register a handler which simply deletes the object via the global pointer. Is that likely to work?

Is there a known good way to approach this problem?

Comminute answered 7/1, 2013 at 16:11 Comment(0)
S
16

In the very worst case, you can always write your own implementation of exit and link it rather than the system's own implementation. You can handle the errors there, and optionally call _exit(2) yourself.

Since you have the library source, it's even easier - just add a -Dexit=myExit flag when building it, and then provide an implementation of myExit.

Submerged answered 7/1, 2013 at 16:14 Comment(2)
Aha! The C++ app's makefile can just redefine exit when compiling the C library - no need to modify the code at all. I'll still need to implement the ability to clean up the entire app from a global handler, but still, this is the best answer so far.Comminute
Hey I'm trying to do this right now on a linux machine, using -Dexit=myExit but unfortunately I come across this error: In file included from /usr/include/c++/7/ext/string_conversions.h:41:0, from /usr/include/c++/7/bits/basic_string.h:6352, from /usr/include/c++/7/string:52.../usr/include/c++/7/cstdlib:146:11: error: ‘::exit’ has not been declared using ::exit; Do you have any ideas for a fix? posted at: #53532050Maccarthy
S
5

install exit handler with atexit and implement the desired behavior

Smacker answered 7/1, 2013 at 16:16 Comment(2)
I did think of this (it's in the question). The problem is that local variables in every function on the call stack are unavailable to a simple handler - hence my thoughts about making the app itself a single object allocated on the heap and deletable via a global pointer.Comminute
This approach is useless in practice because the exit status is not passed to the function you register with atexit.Kyle
S
2

If you want to make the C library more usable from C++, you could perhaps run it in a separate process. Then make sure (with an exit handler or otherwise) that when it exits, your main application process notices and throws an exception to unwind its own stack. Perhaps in some cases it could handle the error in a non-fatal way.

Of course, moving the library use into another process might not be easy or particularly efficient. You'll have some work to do to wrap the interface, and to copy inputs and outputs via the IPC mechanism of your choice.

As a workaround to use the library from your main process, though, I think the one you describe should work. The risk is that you can't identify and isolate everything that needs cleaning up, or that someone in future modifies your application (or another component you use) on the assumption that the stack will get unwound normally.

You could modify the library source to call a runtime- or compile-time-configurable function instead of calling exit(). Then compile the library with exception-handling and implement the function in C++ to throw an exception. The trouble with that is that the library itself probably leaks resources on error, so you'd have to use that exception only to unwind the stack (and maybe do some error reporting). Don't catch it and continue even if the error could be non-fatal as far as your app is concerned.

Sulemasulf answered 7/1, 2013 at 16:27 Comment(0)
L
1

If the call exit and not assert or abort, there are a few points to get control again:

  • When calling exit, the destructors for objects with static lifetime (essentially: globals and objects declared with static) are still executed. This means you could set up a (few) global "resource manager" object(s) and release the resources in their destructor(s).
  • As you already found, you can register hooks with atexit. This is not limited to one. You can register more.

If all else fails, because you have the source of the library, you can play some macro tricks to effectively replace the calls to exit with a function of your own that could, for example, throw an exception.

Lathy answered 7/1, 2013 at 16:52 Comment(0)
L
0

Update: I found this solution have many flaws which may cause some system library got into wrong status. (for example lock may not be about to work). The problem is you may not be the first one who runs the atexit_handler, if some other libraries register an atexit handler.

If you don't have any other atexit handler, you can register one which 'throw' a C++ exception and you catch it by yourself. For example:

void atexit_handler() {
    throw std::runtime_error("exit from user library");
}

void call_library_func() {
    std::atexit(atexit_handler);
    try {
        library_function_you_cannot_change();
    } catch (std::runtime_error e) {}

    // now you can do other things...
}

This may be annoying once you still want to call exit(). However, since you cannot unregister the handler, the only way to control it is to use a global variable.

static bool _block_exit = false;
void atexit_handler() {
    if (_block_exit)
        throw std::runtime_error("exit from user library");
}
Loux answered 5/3 at 5:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.