[OpenVMS documentation]
[Site home] [Send comments] [Help with this site] [How to order documentation] [OpenVMS site] [Compaq site]
Updated: 11 December 1998

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 dynamic library and C language header file (pthread_exception.h) that together provide an interface for defining and handling exceptions. It is designed for use with any DECthreads set of interface routines. If the DECthreads exception package is used, your application must be linked with the DECthreads libpthread library.

5.1.1 Supported Programming Languages

The DECthreads exception package is most useful when you are programming in the C language. The DECthreads exception package must be used with the DECthreads pthread interface.

You can use the C language exception library to catch DECthreads exceptions, but you cannot use the C++ language exception library to catch DECthreads exceptions. However, you cannot use the C language or C++ language exception library to define or raise DECthreads exceptions.

Because the DEC C++ runtime library and the POSIX thread runtime environment both use your platform's underlying exceptions mechanisms, the DEC C++ runtime library is able to run destructors an any exit from a block, including the exception handler code blocks defined using the DECthreads exception package.

5.1.2 Relation of Exceptions to Return Codes and Signals

Although the DECthreads pthread interface reports errors by returning nonzero return codes, DECthreads uses exceptions in the following cases:

5.2 Why Use Exceptions

An exception is an object that describes an error condition. 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.

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

Finally, 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 reserved by DECthreads, 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.

Specifically, you must:

  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 raised.
  4. Associated with each exception scope, use the DECthreads CATCH macro to define a block of exception handler code for each exception object that can be raised and to which your program must respond at this point in its work. In this code your program perform activities to repsond to a particular error condition.
  5. Associated with each exception scope, use the DECthreads CATCH_ALL macro to define an exception handler to catch any exception that your program code does not raise---that is, that can be raised by a facility that your program uses, including the host operating system itself. Because your program code does not raise 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 an exception handler, use the DECthreads FINALLY macro to define finalization code, also known as epilogue code, that is always executed, regardless of whether the code in the associated exception scope raised an exception. If this code is reached, 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 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. In C language terms, the exception object can have local scope, module-wide scope, or global scope.

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 and 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

 
   int  read_tape(void) 
   { 
      int ret; 
 
      if (tape_is_ready) { 
         static EXCEPTION parity_error;     /* Declare it */ 
         EXCEPTION_INIT (parity_error);     /* Initialize it */ 
         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 called an 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, program terminating\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, program terminating\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 most appropriate for an exception caught in a CATCH_ALL block. Because this code block catches exceptions that are not "known" to your program's code, it is unlikely that your code is able to fully recover from the error condition that the exception represents.

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 without an exception being 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 exceptions (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 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.

The DECthreads exception package supports several operations on exceptions. Operations on exceptions allow a program to report and handle errors. If an exception is handled properly, the program can recover from certain errors. For example, if an exception is raised from a parity error while reading a tape, the recovery action might be to retry 100 times before giving up.

5.4.1 Declaring and Initializing Exception Objects

In the DECthreads exception package, an exception is a statically allocated variable of type EXCEPTION. Declaring and initializing an exception object documents that a program reports or handles a particular error.

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.

In general, 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 object that has been properly initialized, only an address value is passed to the current exception scope. This form of DECthreads exception object is called an address exception, because the object's value is the address in your program where an error condition was detected. Your program code that handles address exceptions is fully portable among DECthreads-supported platforms.

Use address exceptions if the error conditions that can occur in your program are not assigned to a range of status codes. 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 uses the DECthreads exception package's pthread_exc_set_status_np() routine to assign a DIGITAL UNIX errno code (or OpenVMS condition code or Win32 status code) to the exception object. This form of DECthreads exception object is called a status 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.

When a facility called by DECthreads raises a system-level exception, DECthreads and its clients can catch the exception using a DECthreads status exception. Similarly, when a routine in your code raises a DECthreads exception, the calling routine might handle it using facilities provided by the language or platform.

Given two different exception objects that have been set with the same status value, the DECthreads exception package considers the two objects as functionally identical. For example, if one of the two exceptions is raised, it can be caught by specifying another exception object that has been set to the same status value. In contrast, DECthreads never considers two distinct address exception objects to be identical.

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 or CATCH_ALL 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 entering 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 exception's stack of scope handles the exception, 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

 
   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) { 
         static EXCEPTION parity_error;     /* Declare it */ 
 
         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 can be raised. That is, the block contains code that invokes, or directly or indirectly calls other code that invokes, the DECthreads exception package's RAISE macro when the program detects an error condition. Any exception raised within the block, or within any routines called directly or indirectly within the block, must 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.

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 or code that explicitly or implicitly responds to the error.
  2. Invoking the handler code that is found.
  3. Invoking on optional block of finalization code (or epilogue code), regardless of whether an exception handler was found for the raised exception.

If the exception scope within which an exception is raised does not define a handler or finalization block, then DECthreads simply "tears down" the current exception 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, 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 "catches" that exception, or that defines an epilogue block for finalization processing. See Section 5.4.3 for further details.


Previous Next Contents Index

[Site home] [Send comments] [Help with this site] [How to order documentation] [OpenVMS site] [Compaq site]
[OpenVMS documentation]

Copyright © Compaq Computer Corporation 1998. All rights reserved.

Legal
6101PRO_009.HTML