Document revision date: 19 July 1999 | |
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 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:
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:
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 */ |
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 */ } } |
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 |
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 */ |
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:
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 |
privacy and legal statement | ||
6101PRO_009.HTML |