Handling critical errors

After reading a little in this thread, I've come to the conclusion that relying on exit() to clean up after you is generally not a good idea.

What would you consider to be the best way to handle critical errors without some crazy C++ like exception handling system?

I guess you could add atexit() after every memory allocation that you know is going to remain in memory during the program's entire lifespan, but how should you deal with objects with an unknown lifespan?

This is just a very crude example, but assume that you want to terminate the program if the foo() call fails. How should you deal with freeing the previously allocated memory in a situation like this one? Using atexit here could get a little tricky since you could end up in a situation where you might've already freed some of the dynamically allocated memory. I guess one way to deal with this could be to set the memory pointer to NULL after you free it, and then check for NULL in your atexit handling.

Normal memory allocations, in major desktop OS's such as Windows, Linux or MacOS should be no problem - they get cleaned up on exit. Same applies for files and other such things. I'm fairly convinced those OS's also clean up any handles (files, shared memory and such things).

On the other hand, if you have some sort of special resources (e.g. semaphores or similar) that is shared between several processes, it's possible that you could fall out of the program without cleaning up those - that could be a problem. The solution here would be to clean up such things before you exit. The easy way to solve that would be to change all calls to exit() into calls to "myExit()" or "panic()" or some such, and let that function deal with cleaning up anything that needs cleaning up - exactly how you go about figuring out what needs cleaning up is an interesting challenge of course.

--
Mats

Last edited by matsp; 03-24-2009 at 12:12 PM.

Compilers can produce warnings - make the compiler programmers happy: Use them!
Please don't PM me for help - and no, I don't do help over instant messengers.

I guess you could add atexit() after every memory allocation that you know is going to remain in memory during the program's entire lifespan, but how should you deal with objects with an unknown lifespan?

This is just a very crude example, but assume that you want to terminate the program if the foo() call fails. How should you deal with freeing the previously allocated memory in a situation like this one?

If a critical error causes the whole process to end, which is what atexit() is for, you do not have to free() anything. The entire thing is freed by the operating system, which is why the process is finished. atexit() is more for taking care of things the OS won't, such as saving the current state of a data file, dealing with network connections appropriately, etc.

free() is for freeing memory while the process is running, not when it is finished. Consider this loop:

If the free call were not there, at each iteration of the loop *tmpbuffer gets reassigned to a new block of memory -- but the last one is still held and protected. Since you don't have a pointer to it, it's useless, so for every iteration of the while loop you will leak sizeof(input) bytes.

If a critical error causes the whole process to end, which is what atexit() is for, you do not have to free() anything. The entire thing is freed by the operating system, which is why the process is finished. atexit() is more for taking care of things the OS won't, such as saving the current state of a data file, dealing with network connections appropriately, etc.

It's not defined, though, what the OS will and will not "take care of." You can assume that memory is freed, files are closed, etc. on an OS like Windows or Linux, but take your code to some embedded environment and this may not be true.

To address the initial question, exception-handling systems don't have to be "crazy." In many cases they are the right method. I'm not talking about hacks using setjmp/longjmp, but some kind of structured unwinding mechanism can be very useful.

So, to give a bit more inspiration, I've created cleanup handler system in the past that work like this:

A global (or thread-specific) pointer points to the head of a linked list of cleanup handler frames. The frames act as containers for one or more cleanup handlers. A frame either suceeds or fails. If it succeeds, no cleanup handlers are called. If it fails, all the handlers are called.

When you enter an operation that allocates resources, you create a cleanup frame. As you allocate resources, you push cleanup handlers onto this frame. If you reach the end of your function successfully, you discard the cleanup frame. If you fail, you invoke it, which autocleans the allocated resources.

You can mark frames with a flag indicating that they should always execute. Into these frames you can dump your global cleanup.

Thanks, that's pretty much what I had in mind. Is this something that you've had to use often?

Not so often these days since I mostly code C++ now. The last serious project where I used such a thing was a special-purpose shell. When launching pipelines there are a lot of things which have to happen -- opening pipes, dup'ing file descriptors, forking things, etc. Without a structured way of dealing with failures it would have been almost impossible to keep everything straight and avoid resource leaks.

And as an implementation hint... What I've described (a linked list of frames, each of which points to a linked list of cleanup handlers) is easy to code, but may not be suitable for an embedded environment. You don't necessarily want to call malloc()/free() a bunch of times as you build and then tear down the frames.

As well, if you are encountering memory-related conditions that require cleanup, you probably don't want to be calling dynamic memory functions while handling that.

It is possible to represent not just the handlers but the frames using such a structure. To open a frame, you put a marker on the stack first (i.e. use NULL for both cleanup and arg). Then you push handlers as usual. When you go to pop the frame, you simply invoke handlers until you reach the marker, and remove it.

I like brewbuck's approach for modules that implement their own memory management, but for a program as a whole, there's another approach which I consider to be slightly more elegant: design your program to have only one call to exit(), probably the last line of main(). Every function does its own cleanup and may return some value that indicates the caller to do the same. This way, resource allocation and deallocation are next to each other and there's no need to introduce another layer of abstraction/complexity. This has the additional benefit that you will get a warm feeling if Edsger Dijkstra is watching over you shoulder while you're coding.

Of course, this approach doesn't work with fork() and SIGTERM, because they introduce new exit points.