Guide to DECthreads


Previous | Contents

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, we refer to this pair of macros 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, in turn, 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 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.

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


Previous | Next | Contents