Document revision date: 30 March 2001
[Compaq] [Go to the documentation home page] [How to order documentation] [Help on this site] [How to contact us]
[OpenVMS documentation]

Guide to the POSIX Threads Library


Previous Contents Index

5.3.4 Reraising an Exception

Reraising an exception means to pass it to the next outer exception scope for further processing. Your program should take this step for a given exception when it must respond to the error condition but cannot completely recover from it.

As shown in Example 5-3, within a CATCH or CATCH_ALL code block, your program can invoke the RERAISE macro to pass a caught exception to the next outer exception scope in your program. If there is no next outer TRY block, the default handler for unhandled exceptions receives the exception, produces a default error message that identifies the unhandled exception, then terminates the process.

Reraising is particularly appropriate for an exception caught in a CATCH_ALL block. Because this code block may catch exceptions that are unexpected by your program's code, it is unlikely that your code is able to fully recover from the error condition that the exception represents. Therefore, your code should allow the exceptions to continue to propagate, either so that it will reach a handler that can deal with it properly or the process can be terminated safely.

5.3.5 Expressing Epilogue Actions

Example 5-4 demonstrates the use of the optional FINALLY block.

Example 5-4 Defining Epilogue Actions Using FINALLY

 
   int *local_mem; 
 
   local_mem = malloc (sizeof (int)); 
   TRY {                      /* An exception can be raised within this scope */ 
      operation (local_mem);  
   } 
   FINALLY { 
      free (local_mem); 
   } 
   ENDTRY 
 

A FINALLY block catches an exception and implicitly reraises the exception for the next outer exception scope to handle. The actions defined by a FINALLY block are also performed on normal exit from the TRY block if no exception is raised. This means that those actions need not be duplicated in your code.

Do not combine a FINALLY block with either a CATCH block or CATCH_ALL block in the same TRY block.

5.4 Exception Objects

This section describes the attributes of exception objects (that is, the EXCEPTION type) and the behavior of the exceptions package's exception handling macros (that is, RAISE and RERAISE , TRY , CATCH and CATCH_ALL , and FINALLY ).

An exception object is a data object that represents an error condition that has occurred in a particular context. The error condition can be detected by the operating system, by the native programming language, by another programmatic facility that your program calls, or by your own program. In the exceptions package, it is a statically allocated variable of type EXCEPTION .

5.4.1 Declaring and Initializing Exception Objects

The EXCEPTION type is designed to be an opaque type and should only be manipulated by the exceptions package routines. The actual definition of the type may differ from one release to another. The EXCEPTION type is defined in the pthread_exception.h header file.

You should declare the type as static or extern . For example:


 
   static EXCEPTION an_error; 
 

Because on some platforms an exception object may require dynamic initialization, the exceptions package requires a run-time initialization call in addition to the declaration. The initialization routine is a macro named EXCEPTION_INIT . The name of the exception is passed as a parameter.

The following code fragment shows how a program declares and initializes an exception object:


 
   EXCEPTION parity_error;           /* Declare it */ 
 
   EXCEPTION_INIT (parity_error);    /* Initialize it */ 
 
 

5.4.2 Address Exceptions and Status Exceptions

By default, when your program raises an exception using an exception object that has been properly initialized, the exception is identified by the address of the exception object. This form of exception object is called an address exception. Your program code that handles address exceptions is fully portable among supported platforms because address exceptions contain nothing that is platform dependent.

Use address exceptions if the error conditions that report in your program do not correspond to a system status code. Address exceptions are always unique, so using them cannot cause a "collision" with another facility's status codes and possibly lead inadvertently to handling the wrong exception.

Alternatively, after initializing an exception object and before the exception can be raised, your program can assign a status value to it. The status value is typically an operating system-specific status code that represents a particular error condition. That is, your program can use the exceptions package's pthread_exc_set_status_np() routine to assign a C errno code on Tru64 UNIX or a condition code on OpenVMS to the exception object. This form of exception object is called a status exception.

Given two different exception objects that have been set with the same status value, the exceptions package considers the two objects as representing the same exception. For example, if one of the two objects is used to raise an exception, the exception can be caught by specifying the other exception object that has been set to the same status value. In contrast, the Threads Library never considers two distinct address exception objects to match the same exception.

Using status exceptions can make sense if your program's target platform supports a universal definition of error status. That is, a status exception has the advantage of having some global meaning within your program and with respect to other libraries that your program uses. Your program can interpret, handle, and report the values used in status exceptions in a "centralized" manner, regardless of which facility in your program defines the status value.

5.4.3 How Exceptions Terminate

Threads Library exceptions are terminating exceptions. This means that after a thread raises a particular exception, the thread never resumes execution in the code that immediately follows the statement that invokes the RAISE macro.

Instead, raising the exception causes the thread to resume execution at the appropriate block of handler code (that is, program statements in a CATCH , CATCH_ALL or FINALLY block) that is declared in the current exception scope. If the handler in the current exception scope contains a RERAISE statement, control reverts to the appropriate handler in the next outer exception scope.

Propagation of the exception---that is, transfer of control to an outer exception scope after executing the RERAISE statement---continues until control enters a CATCH or CATCH_ALL block that does not end with a RERAISE statement; after that block's statements are executed, program execution continues at the first statement after the ENDTRY statement that terminates that exception scope.

When any thread raises an exception, if no exception scope in that thread handles the exception without reraising it, the Threads Library terminates the process, regardless of the state of the process' other threads. Termination prevents the unhandled error from affecting other areas of the process.

5.5 Exception Scopes

An exception scope serves two purposes:

Use the TRY / ENDTRY pair of macros to define an exception scope. (Throughout the discussion, this pair of macros is referred to simply as the TRY macro.) The TRY macro defines the beginning of an exception scope, and the ENDTRY macro defines the scope's end.

Example 5-5 illustrates how a program defines an exception scope that encloses one operation, a call to the read_tape() routine.

Example 5-5 Defining an Exception Scope

   EXCEPTION parity_error; 
 
   int  my_function(void) 
   { 
      TRY {             /* Beginning of exception scope */ 
         read_tape ();  /* Operation(s) whose execution can raise an exception */ 
      } 
      ENDTRY            /* End of exception scope */ 
   } 
 
 
 
   int  read_tape(void) 
   { 
      int ret; 
 
      if (tape_is_ready) { 
 
         EXCEPTION_INIT (parity_error);     /* Initialize it */ 
         ret = read(tape_device); 
         if (ret = BAD_PARITY) 
            RAISE (parity_error);           /* Raise it */ 
      }                                       
   } 
 

Defining an exception scope identifies a block of code in which an exception will be handled if it is raised. Any exception raised within the block, or within any routines called directly or indirectly within the block, will pass through the control of this scope.

Because your program can detect different error conditions at different points in the code, your program can define more than one exception scope within its routines.

One exception scope cannot span the boundary of another exception scope. That is, it is invalid for one exception scope to contain only the beginning (the invocation of the TRY macro) or end (the invocation of the ENDTRY macro) of another exception scope. However, they may be nested--in fact, you can use TRY blocks not only inside other TRY blocks, but inside CATCH and FINALLY blocks as well.

5.6 Raising Exceptions

After your program declares and initializes an exception object, your program raises that exception when it detects an error condition. Use the exceptions package's RAISE macro to raise an exception.

When your program raises an exception, it reports an error not by returning a value, but by propagating the exception. Propagating an exception takes place in a series of steps, as follows:

  1. The program searches in the current scope, then in the next outer scope and so on, for an exception handler that explicitly or implicitly responds to the error (such as a CATCH , CATCH_ALL or FINALLY block).
  2. The program invokes the handler code that is found.
  3. If the exception is reraised, then the process resumes with the first step and the next outer scope.

If the exception scope within which an exception is raised does not define a handler block, then the Threads Library simply "tears down" the current execution scope as the exception propagates up the stack of exception scopes. This is also referred to as "unwinding" the stack.

Example 5-6 illustrates how a program raises an exception.

Example 5-6 Raising an Exception

 
   error = get_data(); 
   if (error) { 
      EXCEPTION parity_error;           /* Declare it */ 
 
      /* Initialize exception object and 
          optionally set its status code */ 
 
      EXCEPTION_INIT (parity_error); 
      pthread_exc_set_status_np (&parity_error, ENOMEM); 
      RAISE (parity_error);             /* Raise it */ 
   } 
 

Threads Library exceptions are classified as terminating exceptions because after an exception is raised even if it is handled, the thread does not resume its execution at the point where the error condition was detected. Rather, execution resumes within the innermost exception scope that defines a handler block that either explicitly or implicitly matches that exception, or that defines an epilogue block for finalization processing. See Section 5.4.3 for further details.

5.7 Exception Handling Macros

The exceptions package allows your program to define an exception scope and to define and associate one or more blocks of code, each called an exception handler, with that scope. The exception handler takes appropriate actions in response to an error condition. "Appropriate actions" can mean merely cleaning up a routine's local context and propagating the exception to the next outer exception scope, or it can mean fully responding to the error in such a manner that allows the routine with the handler to continue its work.

5.7.1 Context of the Handler

An exception handler always runs within the context of the thread that generates the exception. Exceptions are synchronous events, like an access violation or segmentation fault, that are tied to a specified thread's context.

Exception handlers are also closely tied to the execution context of the block that declares the handler. Thus, in the exceptions package, exception handlers are attached, which means that the handler code appears within the same routine where the specified exceptions are raised (directly or indirectly). This allows the code to access local commands when an exception occurs with that exception scope, and allows the error handling code to be positioned "close" to the code with which it is associated for readability and maintainability.

5.7.2 Handlers and Macros

Unlike a signal handler routine, an exception handler can call any pthread routine.

Exception handler code is invoked when a matching exception propagates within the execution scope of the associated exception scope.

Use the exceptions package's CATCH macro to define an exception handler code block that is invoked when an exception matching the macro's specified exception object is propagated within the associated exception scope. Use the exceptions package's CATCH_ALL macro to define an exception handler code block that is invoked when any other exception is propagated within the associated exception scope.

An exception handler's code can reraise an exception. That is, the code can propagate an exception to the next outer exception scope for further processing. Use the exceptions package's RERAISE macro to do so. If appropriate, a handler may instead use the RAISE macro to raise a different exception.

Another form of exception handler code is finalization code, or epilogue code. You can define a block of epilogue code and associate it with an exception scope. When an exception is raised, epilogue code performs your cleanup actions within the current exception scope (such as releasing resources), then automatically propagates the raised exception to outer scopes for further processing. Additionally, finalization occurs even if no exception was raised, so that resources are always released without duplication of code.

Use the exceptions package's FINALLY macro to define an epilogue code block. Note that, for a given exception scope, FINALLY blocks and CATCH and CATCH_ALL blocks are mutually exclusive.

Each of these macros is discussed in greater detail in the following sections.

5.7.3 Catching Specific Exceptions

The exception scope can express interest in catching a particular exception by specifying a corresponding exception object as the argument in a statement that invokes the CATCH macro. When an exception reaches the exception scope, control is transferred to the first CATCH code block that specifies a matching exception object. If there is more than one CATCH code block that specifies a matching object within a single TRY/ENDTRY scope, only the first one gains control. (Thus, there is no point in having two CATCH blocks with matching or equivalent exceptions.)

To catch an address exception, the CATCH macro must specify the name of the exception object used in the invoked RAISE macro. However, status exceptions can be caught using any exception object that has been set to the same status code as the exception that was raised.

Example 5-7 shows an exception scope with one exception handler that uses the CATCH macro to catch a specific exception ( parity_error ) and to specify a recovery action (produce a message).

Example 5-7 Catching a Specific Exception Using CATCH

 
   TRY { 
      read_tape (); 
   } 
   CATCH (parity_error) { 
      printf ("Oops, parity error, read aborted\n"); 
      printf ("Try cleaning the heads!\n"); 
      RERAISE; 
   } 
   ENDTRY 
 

In this example, after catching the exception and executing the recovery action, the handler explicitly reraises the caught exception. This causes the exception to propagate to the next outer exception scope.

Typically, you code one exception handler for each distinct error condition that can be raised anywhere in the program's execution within the associated exception scope.

If it is appropriate for the caught exception to be propagated to the next higher exception scope, the CATCH code block can use the RERAISE macro as its last action to explicitly raise the same exception again.

5.7.4 Catching Unspecified Exceptions

The exception scope can express interest in catching all exceptions by coding an exception handler that uses the CATCH_ALL macro.

There must be only one CATCH_ALL code block within an exception scope. Note that it is invalid for a CATCH macro to follow a CATCH_ALL macro within an exception scope.

Example 5-8 demonstrates using the CATCH_ALL macro to define an exception handler for expressing actions in response to exceptions that are not being uniquely handled on a per-exception basis in the program's code.

Example 5-8 Catching an Unspecified Exception Using CATCH_ALL

 
   int *local_mem; 
 
   local_mem = malloc (sizeof (int)); 
   TRY { 
      operation(local_mem);      
      free (local_mem); 
   } 
   CATCH (an_error) { 
      printf ("Oops; caught one!\n"); 
      free (local_mem); 
   } 
   CATCH_ALL { 
      free (local_mem); 
      RERAISE; 
   } 
   ENDTRY 
 

Because you cannot necessarily predict all possible exceptions that your code might encounter, you cannot assume that your code can recover in every possible situation. Therefore, your CATCH_ALL code block should explicitly reraise each caught exception as its final action; this allows an outer exception scope also to catch the same exception and to respond appropriately for its own context.

5.7.5 Reraising the Current Exception

Within an exception scope's CATCH or CATCH_ALL code blocks, you can invoke the RERAISE macro to reraise a caught exception. This allows the next outer exception scope to handle the exception as it finds appropriate. Invoking the RERAISE macro is valid only within a CATCH or CATCH_ALL code block.

Use the RERAISE macro in a CATCH or CATCH_ALL code block that must restore some permanent program state (for example, releasing resources such as memory or a mutex) but does not have enough context about the detected error condition or sufficient reason to attempt to recover fully. For example, a CATCH_ALL code block should always reraise the caught exception as its last action, because the exception handler cannot recover fully from the error since it does not know what the error specifically was.

Refer to Example 5-8 for an example of how a program invokes the RERAISE macro as the last action in a CATCH_ALL code block.

5.7.6 Defining Epilogue Actions

Some of your program's CATCH or CATCH_ALL code blocks may catch exceptions only for the purpose of performing cleanup actions, such as releasing resources. In many cases, these actions are performed when the TRY code block exits normally or after an exception has been caught. This requires duplicating code in the CATCH_ALL code block and following the exception scope (for the case when an exception does not occur).

The exceptions package's FINALLY macro defines a code block that catches an exception and then implicitly reraises that exception for the next outer exception scope to handle. The actions in a FINALLY code block are also performed when the scope exits normally (that is, when no exception is raised), so that they need not be coded more than once.

Example 5-9 demonstrates the FINALLY macro.

Example 5-9 Defining Epilogue Actions Using FINALLY

 
   pthread_mutex_lock (&some_object.mutex); 
   some_object.num_waiters = some_object.num_waiters + 1; 
   TRY { 
       while (! some_object.data_available) 
           pthread_cond_wait (&some_object.condition, &some_object.mutex); 
       /* The code to act on the data_available goes here */ 
   } 
   FINALLY { 
       some_object.num_waiters = some_object.num_waiters - 1; 
       pthread_mutex_unlock (&some_object.mutex); 
   { 
   ENDTRY 
 

In this example, if the thread was canceled while it was waiting, the pthread_cancel_e exception would propagate out of the pthread_cond_wait() call. The operations in the FINALLY code block release the mutex, after ensuring that the shared data associated with the lock is correct for the next thread that acquires the mutex.

Note

Do not define a FINALLY code block if your exception scope uses a CATCH or CATCH_ALL code block. Doing so results in unpredictable behavior.


Previous Next Contents Index

  [Go to the documentation home page] [How to order documentation] [Help on this site] [How to contact us]  
  privacy and legal statement  
6101PRO_010.HTML