[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

5.7 Exception Handling Macros

The DECthreads exception 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 purpose of an exception handler is to take appropriate actions, in context, 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 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 DECthreads exception package, exception handlers are attached, which means that the handler code appears within the same routine where the specified exceptions are raised. This allows the programmer to see what actions the program takes when an exception occurs with that exception scope.

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 the exception specified in the handler is raised (or reraised) or when any unspecified exception is raised (or reraised) within the lexical scope of the associated exception scope.

Use the DECthreads exception package's CATCH macro to define an exception handler code block that is invoked when the macro's specified exception object is raised within the associated exception scope. Use the DECthreads exception package's CATCH_ALL macro to define an exception handler code block that is invoked when any non-specified exception object is raised (or reraised) within the associated exception scope.

An exception handler's code can reraise an exception. That is, the code can pass an exception object to the next outer exception scope for further processing. Use the DECthreads exception package's RERAISE macro to do so.

Related to exception handler code is finalization code, or epilogue code. You can define a block of epilogue code and associate it with an exception scope. After an exception has been raised (or reraised), epilogue code performs cleanup actions within the current exception scope (such as releasing resources) then passes on 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 DECthreads exception package's FINALLY macro to define an epilogue code block. Note that, for a given exception scope, you code a FINALLY block instead of coding CATCH and CATCH_ALL blocks.

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 specific exception by naming it 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 the exception by name. If there is more than one CATCH code block that specifies the same exception object by name within a single TRY/ENDTRY scope, only the first one gains control.

To catch an address exception, the CATCH macro must specify the name of the exception object as used in the invoked RAISE macro. In general, your program should raise and catch using the same exception object, even when using status exceptions. 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, program terminating\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 object 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 call stack that is also within the associated exception scope.

If it is preferable for the caught exception to be propagated to the next higher exception scope, the CATCH code block can use the RERAISE macro 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 not explicitly raised your 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 
 

It is best practice for your program to reraise any exception that is caught by a CATCH_ALL code block. (Not doing so is called absorbing the exception.) It is inappropriate to absorb a raised exception that your code is not explicitly aware of.

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 object. This allows the next outer exception scope to handle the exception as it finds appropriate. Invoking the RERAISE macro is valid only with 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 to attempt to recover fully. Thus, a CATCH_ALL code block should always reraise the caught exception as its last action.

Example 5-9 demonstrates invoking the RERAISE macro as the last action in a CATCH_ALL code block.

Example 5-9 Reraising an Exception Using RERAISE

 
   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; 
   } 
 

5.7.6 Defining Epilogue Actions

Some of your program's CATCH or CATCH_ALL code blocks catch exceptions only to perform cleanup actions, such as releasing resources. In many cases, these actions are performed whether the TRY code block exits normally or after an exception has been caught. Under other exception models, this requires duplicating code in the CATCH_ALL code block and following the exception scope (in case an exception does not occur).

The DECthreads exception 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, without catching an exception), so that they need not be coded more than once.

Example 5-10 demonstrates the FINALLY macro.

Example 5-10 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 call to pthread_cond_wait() could raise the pthread_cancel_e exception. The operations in the FINALLY code block ensure 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.

5.8 Operations on Exceptions

In addition to raising, catching, and reraising exception objects, the DECthreads exception package supports the following API-level operations on exception objects:

The following sections discuss these operations.

5.8.1 Referencing the Caught Exception

Within a CATCH or CATCH_ALL code block the caught exception object can be referenced by using the THIS_CATCH symbol.

The THIS_CATCH definition has a type of EXCEPTION *. This value can be passed to the pthread_exc_get_status_np(), pthread_exc_report_np(), or pthread_exc_matches_np() routines, as described in Section 5.8.3, Section 5.8.4, and Section 5.8.5.

Note

Because of the way that the DECthreads exception package propagates exception objects, the address contained in THIS_CATCH might not be the actual address of a DECthreads address exception. To match THIS_CATCH against known exceptions, use the pthread_exc_matches_np() routine, as described in Section 5.8.5.

5.8.2 Setting a System-Defined Error Status

Use the pthread_exc_set_status_np() routine to set a status value in an existing DECthreads address exception object. This transforms the address exception object into a DECthreads status exception object.

This routine's exception object argument must already have been initialized with the DECthreads exception package's EXCEPTION_INIT macro, as described in Section 5.3.1.

In a program that uses DECthreads status exceptions, use this routine to associate any system-specific status value with the specified address exception object. Note that any exception objects set to the same status value are considered equivalent by DECthreads.

Example 5-11 demonstrates setting an error status in a DECthreads address exception object.

Example 5-11 Setting an Error Status in an Exception Object

 
   void pthread_exc_set_status_np (EXCEPTION *exception, 
                                   unsigned long code); 
   static EXCEPTION an_error; 
 
   EXCEPTION_INIT (an_error); 
   unsigned long status_code = ENOMEM; 
 
   /* Import status code into an existing, initialized, 
       address exception object */ 
 
   pthread_exc_set_status_np (&an_error, status_code); 
 

Note

On OpenVMS systems:

DECthreads exception status values always have a SEVERE severity level. If necessary, calling the pthread_exc_set_status_np() routine modifies the severity level of the status code to SEVERE.

5.8.3 Obtaining a System-Defined Error Status

In a program that uses DECthreads status exceptions, use the
pthread_exc_get_status_np() routine to obtain the status value from a DECthreads status exception, such as after an exception is caught. If the routine's exception argument is a status exception, it sets the status code argument and returns 0 (zero); otherwise, it returns [EINVAL] and does not set the status value argument.

Example 5-12 demonstrates using the pthread_exc_get_status_np() routine to obtain the status value associated with a caught DECthreads status exception.

Example 5-12 Obtaining the Error Status Value from a Status Exception Object

 
   int pthread_exc_get_status_np (EXCEPTION *exception, unsigned long code); 
   .
   .
   .
 
   TRY { 
      operation (); 
       } 
   CATCH_ALL { 
      unsigned long  status_code; 
 
      if (pthread_exc_get_status_np (THIS_CATCH, &status_code) == 0 
           && status_code == SOME_ERROR) 
        fprintf (stderr, "%Exception %s caught from system.\n", SOME_ERROR); 
      else 
        pthread_exc_report_np (THIS_CATCH); 
      } 
   ENDTRY 
 

5.8.4 Reporting a Caught Exception

Use the pthread_exc_report_np() routine to produce a message that reports what a given exception object represents. Your program calls this routine within a CATCH, CATCH_ALL, or FINALLY code block to report on a caught exception.

An exception that has been raised but not caught by a CATCH or CATCH_ALL anywhere in your program causes the DECthreads unhandled exception handler to report the exception and immediately terminate the process. However, you might prefer to report a caught exception as part of your program's error recovery.

The pthread_exc_report_np() routine prints a message to stderr (on DIGITAL UNIX and Windows NT systems) or SYS$ERROR (on OpenVMS systems) that describes the exception.

Each DECthreads-defined exception has an associated message that describes a given error condition. Typically, when the DECthreads exception package is well-integrated with the host platform's status mechanism, external status values can also be reported. On the other hand, when an address exception is reported, DECthreads can only report the fact that an exception has occurred and the address of the exception object.

Example 5-13 demonstrates using the pthread_exc_report_np() routine to report an error.

Example 5-13 Reporting a Caught Exception

 
   void pthread_exc_report_np (EXCEPTION *exception); 
   .
   .
   .
   pthread_exc_report_np (&illinstr); 
 

5.8.5 Determining Whether Two Exceptions Match

The pthread_exc_matches_np() routine compares two exception objects, taking into consideration whether each is an address exception or a status exception. Whenever you must compare two exceptions, use this routine.

Example 5-14 demonstrates how to use the pthread_exc_matches_np() routine to test for the equivalence of two DECthreads exception objects.

Example 5-14 Comparing Two Exception Objects

 
   int pthread_exc_matches_np (EXCEPTION *exception1, 
                               EXCEPTION *exception2); 
   .
   .
   .
   EXCEPTION my_status; 
 
   EXCEPTION_INIT (&my_status); 
   pthread_exc_set_status_np (&my_status, status_code); 
   .
   .
   .
   TRY { 
      .
      .
      .
   } 
   .
   .
   .
   CATCH_ALL { 
      if (pthread_exc_matches_np (THIS_CATCH, &my_status)) 
         fprintf (stderr, "This is my exception\n"); 
      RERAISE; 
   } 
   ENDTRY 
   

5.9 Conventions for Modular Use of Exceptions

This section presents guidelines for using DECthreads exceptions in a modular way, so that independent software components can be written without requiring knowledge of each other.

5.9.1 Develop Naming Conventions for Exceptions

Develop naming conventions for exception objects. A naming convention ensures that the names for exceptions that are declared extern in different modules do not clash. The following convention is recommended:


   facility-prefix_error-name_e 

Example: pthread_cancel_e

5.9.2 Enclose Appropriate Actions in an Exception Scope

In a TRY code block avoid including code that more appropriately belongs outside it (in particular, before it). That is, the TRY macro should guard only operations for which there are appropriate handler operations in the scope's FINALLY, CATCH, or CATCH_ALL code blocks.

A common misuse of a TRY code block is to include code that should be executed before the TRY macro is invoked. Example 5-15 demonstrates this misuse.

Example 5-15 Incorrect Placement of Statements That Might Raise an Exception

 
   TRY { 
       handle = open_file (file_name); 
 
       /* Statements that might raise an exception here */ 
 
   } 
   FINALLY { 
       close (handle); 
   } 
   ENDTRY  
 

In this example, the FINALLY code block assumes that no exception is raised by calling the open_file() routine. If calling open_file() results in raising an exception, the FINALLY code block's close() operation will use an invalid identifier.

Thus, the code in Example 5-15 should be rewritten as shown in Example 5-16.

Example 5-16 Correct Placement of Statements That Might Raise an Exception

 
   handle = open_file (file_name); 
   TRY { 
 
       /* Statements that might raise an exception here */ 
 
   } 
   FINALLY { 
       close (handle); 
   } 
   ENDTRY 
 

The code that is an opening bracket belongs prior to invoking the TRY macro, and the code that is its matching closing bracket belongs in the FINALLY code block.

5.9.3 Raise Exceptions Prior to Performing Side-Effects

Raise exceptions prior to performing side-effects. That is, write routines that propagate exceptions to their callers, so that the routine does not modify any persistent process state before raising the exception. A matching close() call is required only if the open_file() operation is successful. (If an exception is raised, the caller cannot access the output parameters of the function, because the compiler may not have copied temporary values back to their home locations from registers.)

If the open_file() routine raises an exception, the identifier will not have been written, so this open operation must not require that a corresponding close() routine is called when open_file() raises an exception. This property is also what allows the call to be moved to open_file() prior to invoking the TRY macro.

5.9.4 Distinguish Raising Exceptions From Side-Effects

Do not place a return or goto statement between TRY and ENDTRY. It is invalid to return from, branch from, or leave by other means a TRY, CATCH, CATCH_ALL, or FINALLY block. After a given TRY macro is executed, the DECthreads exception package requires that the corresponding ENDTRY macro is also executed.


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_010.HTML