| Previous | Contents | Index |
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:
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:
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:
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 */ |
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 */
}
}
|
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
|
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 */ |
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:
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 |