Tru64 UNIX Guide to DECthreads


Previous Contents Index


Chapter 5
Using the DECthreads Exception Package

This chapter describes how to use the DECthreads exception package and demonstrates conventions for the modular use of DECthreads exceptions in a multithreaded program.

This chapter:

5.1 About the DECthreads Exceptions Package

The DECthreads exception package is a part of the thread library. A C language header file (pthread_exception.h) provides an interface for defining and handling exceptions. It is designed for use with the pthreads interface routines. If the DECthreads exception package is used, your application must be linked with the DECthreads library.

5.1.1 Supported Programming Languages

The DECthreads exception package can be used only when you are programming in the C language. While the DECthreads exceptions will compile under C++, they will not behave properly. In addition, gcc lacks the Compaq C extensions that are needed to interact with the native exception handling system, and will not interoperate correctly with other language exception facilities.

You can use the C language exception handling mechanism (SEH) to catch DECthreads exceptions. You can catch DECthreads exceptions in C++ using catch(...), and propagation of DECthreads exceptions will run C++ object destructors. Currently, C++ code cannot catch specific DECthreads exceptions. Also, DECthreads CATCH, CATCH_ALL and FINALLY clauses will not run when C++ code raises an exception. (These restrictions will be reduced or removed in a future release.)

5.1.2 Relation of Exceptions to Return Codes and Signals

DECthreads uses exceptions in the following cases:

5.2 Why Use Exceptions

An exception is a mechanism for reporting an error condition. An exception is represented by an exception subject. Operations on exception objects allow your program to report and handle errors. If an exception can be handled properly, the program can recover from errors. For example, if an exception is raised from a parity error while reading a tape, the recovery action might be to retry reading the tape 100 times before giving up. However, if the exception is not handled, then the program terminates. Reporting errors via exceptions ensures that the error will not inadvertently go unnoticed and cause problems later.

You use exception programming to identify a portion of a routine, called an exception scope, where a calling thread wishes to respond to particular error conditions or perhaps to any error condition. The thread can respond to each exception in either of two ways:

As a result, you can use the DECthreads exception package to handle thread cancelation and thread exit in a unified and modular manner. Because DECthreads implements both thread cancelation and thread exit by raising exceptions, your code can respond to these events in the same modular manner as for error conditions.

5.3 Exception Programming

Each DECthreads exception object is of the EXCEPTION type, which is defined in the pthread_exception.h header file.

To use exceptions, do the following:

  1. Declare one DECthreads exception object for each distinct error condition of interest to your program.
  2. Code your program to invoke the DECthreads RAISE macro when it detects an error condition.
  3. Code an exception scope, using the TRY and ENDTRY macros, to define the program scope within which an exception might be handled.
  4. Associated with each exception scope, optionally include the DECthreads CATCH macro to define a block of exception handler code for each exception that your program wishes to handle at this point in its work. In this block your program can perform activities to respond to the particular error condition.
  5. Associated with each exception scope, optionally include the DECthreads CATCH_ALL macro to define an exception handler to catch any other exception that might be raised, if your code needs to respond to such errors. Unless your code can fully recover from these exceptions, your handler code must also reraise the caught exception so that the next outer exception scope also has the chance to respond to it.
  6. Associated with each exception scope, instead of defining CATCH and/or CATCH_ALL blocks, use the DECthreads FINALLY macro to define finalization code, also known as epilogue code, that is always executed when control leaves the TRY block, regardless of whether the code in the associated exception scope raised an exception. If this code is reached because of an exception being raised, DECthreads automatically reraises the caught exception and passes it to the next outer exception scope.

When a thread in your program raises an exception, DECthreads determines whether an exception scope has been defined in the current stack frame. If so, DECthreads checks whether there is a specific handler (CATCH code block) for the raised exception or whether there is an unspecified handler (CATCH_ALL or FINALLY code block). If not, DECthreads passes the raised exception to the next outer exception scope that does contain the pertinent code block. Thread execution resumes at that block. Attempting to catch a raised exception can cause a thread's stack to be unwound one or more call frames.

An exception can be caught only by the thread in which it is raised. An exception does not propagate from one thread to another.

5.3.1 Declaring and Initializing an Exception

Before referring to a DECthreads exception object in your code, your program must declare and initialize the object. A DECthreads exception object must be defined (whether explicitly or implicitly) to be of static storage class.

The next sample code fragment demonstrates declaring and initializing an exception object.


 
   static EXCEPTION parity_error;  /* Declare the exception */ 
 
   EXCEPTION_INIT (parity_error);  /* Initialize the exception */ 
 

5.3.2 Raising an Exception

Raise a DECthreads exception to indicate that your program has detected an error condition in response to which the program must take some action. Your program raises the exception by invoking the DECthreads RAISE macro.

Example 5-1 demonstrates how to raise a DECthreads exception.

Example 5-1 Raising an Exception

 
   static EXCEPTION parity_error; 
 
   int  read_tape(void) 
   { 
      int ret; 
 
      EXCEPTION_INIT (parity_error);     /* Initialize it */ 
      if (tape_is_ready) { 
         ret = read(tape_device); 
         if (ret = BAD_PARITY) 
            RAISE (parity_error);           /* Raise it */ 
      }                                       
   } 
 

5.3.3 Catching an Exception

After your program raises a DECthreads exception, it is passed to a location within a block of code in a containing exception scope. The exception scope defines:

Example 5-2 shows a TRY code block with a CATCH code block defined to catch the exception object named parity_error when it is raised within the read_tape() routine.

Example 5-2 Catching an Exception Using CATCH

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

Example 5-3 demonstrates how CATCH and CATCH_ALL code blocks work together to handle different raised exceptions within a given TRY code block.

Example 5-3 Catching an Exception Using CATCH and CATCH_ALL

 
   int *local_mem; 
 
   local_mem = malloc (sizeof (int)); 
   TRY {                /* An exception can be raised within this scope */ 
      read_tape (); 
      free (local_mem); 
   } 
   CATCH (parity_error) { 
      printf ("Oops, parity error, read aborted\n"); 
      printf ("Try cleaning the heads!\n"); 
      free (local_mem); 
   } 
   CATCH_ALL { 
      free (local_mem); 
      RERAISE; 
   } 
   ENDTRY 
 

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 DECthreads 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, so that it will either reach a handler which can deal with it properly or that 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 a CATCH block or CATCH_ALL block in the same TRY block.

5.4 Exception Objects

This section describes the attributes of DECthreads exception objects (that is, the EXCEPTION type) and the behavior of the DECthreads exception 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 DECthreads exception 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 DECthreads exception package routines. The actual definition of the type may differ from one DECthreads 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 DECthreads exception 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 declaring and initializing 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 DECthreads exception object is called an address exception. Your program code that handles address exceptions is fully portable among DECthreads-supported platforms because address exceptions contain nothing which 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 DECthreads exception 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 DECthreads exception object is called a status exception.

Given two different exception objects that have been set with the same status value, the DECthreads exception 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, DECthreads 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

DECthreads 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, DECthreads terminates the process, regardless of the state of the process's 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 defining 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 DECthreads exception package's RAISE macro to raise an exception.

Raising an exception 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. Searching in the current scope, then to 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. Invoking 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 DECthreads simply "tears down" the current execution scope as the exception propagates up the DECthreads stack of exception scopes. This is also referred to as "unwinding" the stack.

Example 5-6 illustrates raising a DECthreads exception.

Example 5-6 Raising a DECthreads 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 */ 
   } 
 

DECthreads 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 explicitly or implicitly matches that exception, or that defines an epilogue block for finalization processing. See Section 5.4.3 for further details.


Previous Next Contents Index