Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 1 - MacApp Theory and Architecture
Chapter 3 - Core Technologies

Failure Handling

MacApp provides a failure-handling mechanism that allows your application to clean up after errors. An application can install failure handlers at multiple points. Failure handlers are declared as local variables, and so are stored on the stack. MacApp connects them into a linked list, with each handler pointing to the next handler in the list, as shown in Figure 3-2. A global variable, gTopHandler, points to the most recently stored failure handler.

Figure 3-2 A linked list of failure handlers, stored on the stack

When an error occurs, the most recent failure handler is retrieved, the previous state of the machine is restored, and execution continues at the location specified by the handler. Optionally, the failure handler may call a specific cleanup routine. On completion, the failure-handling code may return to regular program execution or it may propagate the failure by calling the ReSignal method to retrieve the next failure handler from the list.

You install a failure handler by using the Try macro to bracket code where an error might occur. You follow the Try branch with code to handle the anticipated failure. For example, you can use a failure handler to bracket a memory allocation loop. If an allocation fails, the failure handler jumps to code that frees any memory that has already been allocated. If no error occurs, you call a method to remove the failure handler from the linked list of failure handlers.

Failure handling is described in greater detail in the following sections. For additional code samples and information on how to use failure handlers in your application, see Chapter 24, "Working With Memory and Failure Handling."

The Failure Routine

References to failure handlers are kept in a linked list, pointed to by a global variable, gTopHandler, as shown in Figure 3-2. Each item in the list is an object of type FailInfo, described in the next section. FailInfo objects are declared in the routines that use them, so they are stored on the stack.

When you call the Failure routine, it retrieves the most recent failure handler from the list, then calls the handler's cleanup routine, if it has one. If the failure handler doesn't have a cleanup routine (most don't), Failure calls longjmp, passing the saved state buffer, to restore the previous machine state.

MacApp supplies routines that check for specific types of errors and call Failure if they find one. The names of these routines reflect the kind of error they check for: FailNIL, FailNILResource, FailMemError, FailOSErr, FailResError, FailNoReserve, and FailSpaceIsLow. For more information, see "MacApp Error-Checking Routines," beginning on page 562.

An application can use these MacApp routines, or it can check for an error condition itself and call Failure directly when it detects an error.

The FailInfo Class

MacApp defines the FailInfo class to implement failure handlers. FailInfo includes these features:

The following code fragment demonstrates how a failure handler is installed:

// Declare a failure handler.
FailInfo theFailureHandler;
// Insert the failure handler in the global list.
{  // Code that may cause a failure.
   // If no failure occurs, remove the failure handler.
{  // Code to clean up if an error occurs.
   // In this case, pass error on to next handler.
The macro Try, described in the next section, is executed first. It inserts the failure handler in the list and calls setjmp. If no error occurs, the call to Success removes the failure handler from the list.

If an error does occur, the failure handler is processed and causes execution to continue with the else block of code. If no error occurs, the else block is never executed and the call to Success removes the error handler from the list.

Note that the call to ReSignal, which retrieves the next failure handler from the list and jumps to the location it specifies, is not always necessary--the local code may handle the error condition completely.

The Try Macro

Try is implemented as a macro rather than as a method of the FailInfo class, because the failure-handling code must be inline to properly save the desired machine state. The decision whether or not to put code inline is handled differently by different compilers and can be affected by options such as symbol generation and optimization level. Implementing Try as a macro takes the decision away from the compiler.

Saving and Restoring the Machine State

When a failure handler is initialized, MacApp inserts it in the linked list of failure handlers and calls the C library routine setjmp to save the current machine state in the failure handler's buffer. When a failure occurs, MacApp removes the most recent failure handler from the linked list and calls the longjmp routine to restore the saved state. The application resumes execution as if it had just returned from the setjmp call. The call chain also returns to its previous state, and return statements for any intervening calls are eliminated.

Calling Success to Remove a Failure Handler From the List

Calling Success removes a failure handler when it is no longer needed. Failing to call Success the same number of times you call Try causes a failure handler to be left in the list. This can lead to a program crash for one of two reasons:

An application built with MacApp's debugging support issues a warning if Success is not called as often as Try.

The MAVolatile and MAVolatileInit Macros

A C++ compiler can optimize code by storing local variables in registers. Doing so can cause problems during failure handling because longjmp restores registers to the values they had before the error occurred. Thus a variable you wish to use in your recovery code may be set to an incorrect value.

MacApp supplies the MAVolatile macro to ensure that a variable is kept out of a register. MAVolatile can be called on a variable of any type and is guaranteed to work on any compiler, even those that don't support the volatile keyword. You supply a variable and a type, as in the following line:

MAVolatile(Boolean, pictureIsOpen);
The MAVolatileInit macro is a variation on MAVolatile that both initializes a variable and ensures that it won't be stored in a register. Here you supply a type, a variable, and an initial value. For example, the code

MAVolatileInit(THandleList*, aHandleList, NULL);
initializes the variable aHandleList to NULL and guarantees that it won't be stored in a register.

Don't use the volatile macros with data that will be passed by address or by reference--it won't work without casting or changing the called function to expect a volatile parameter, and it isn't necessary.

Failure Handling Embedded in Objects

MacApp provides a number of classes with embedded failure handling, including CArrayIterator, CTemporaryRegion, and CTempDesc.

Each of these classes has a FailInfo field, fFailInfo, which provides failure handling for the class. These classes provide an example of how to embed failure handling in an object and use a cleanup procedure to handle any failure.

When an array iterator is initialized, the IArrayIterator method sets up the iterator's fFailInfo field by making the following calls:

fFailInfo->SetCleanupProc(CArrayIterator::CallCleanup, this);
The Reset method sets the failure handler to a safe initial state. The SetCleanupProc method inserts the failure handler in the list and sets its cleanup procedure to the iterator's CallCleanup method.

When a failure occurs, MacApp's Failure routine retrieves the first failure handler from the list and calls its cleanup procedure, if it has one. In this case it calls the array iterator's CallCleanup method, which in turn calls the iterator's Cleanup method. Cleanup frees the iterator's dynamic storage, if necessary. If no failure occurs, the destructor method, ~CArrayIterator, calls Success to remove the failure handler from the list. (Iterators are discussed in "Lists and Iteration," beginning on page 61.)

You may notice examples in the MacApp class library where an extra code block is inserted around the creation of an iterator object in a situation that requires failure handling. This is done to control the creation and destruction of the iterator's embedded failure handler. By specifically controlling the scope in which the iterator exists, there is no ambiguity about when the iterator is deleted and its failure handler is removed from the list. In addition, controlling the scope may be more efficient and require fewer failure handlers to exist at one time.

The following sample shows how to introduce an extra code block to control the scope of a failure handler embedded in an iterator.

FailInfo firstFailureHandler;
   // Code that fails here is caught by the first failure handler.

   // Add an extra block here to control destruction of iterator.
      // Note: iterator's constructor installs a failure handler.
      CObjectIterator iter(aFileList);
      for (aFile = (TFile *)iter.FirstObject();
                        iter.More() && !savedError;
                        aFile = (TFile *)iter.NextObject())
         // Code to open a document for each file in the list.
         // Could run out of memory or experience a file error.
         // Code that fails here is caught by the "iter" object's
         // embedded failure handler.
      } // End for loop

   }  // End extra block. The iterator goes out of scope and 
      // its destructor removes its embedded failure handler
      // from the linked list of failure handlers.

   // Could have more code here.

}  // End Try for firstFailureHandler.
   // Error recovery code for firstFailureHandler block.
When the code reaches the end of the extra block, the iterator goes out of scope and its destructor calls Success to remove the embedded failure handler from the list.

Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996