Previous | Contents | Index |
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 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, either so that it will reach a handler that can deal with it
properly or 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 either a
CATCH
block or
CATCH_ALL
block in the same
TRY
block.
5.4 Exception Objects
This section describes the attributes of exception objects (that is, the EXCEPTION type) and the behavior of the exceptions 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 exceptions 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 exceptions package routines. The actual definition of the type may differ from one 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 exceptions 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 how a program declares and initializes 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 exception object is called an address exception. Your program code that handles address exceptions is fully portable among supported platforms because address exceptions contain nothing that 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 exceptions 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 exception object is called a status exception.
Given two different exception objects that have been set with the same status value, the exceptions 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, the Threads Library 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
Threads Library 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, the Threads Library
terminates the process, regardless of the state of the process' 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 how a program defines 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 exceptions package's RAISE macro to raise an exception.
When your program raises an exception, it 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 the Threads Library simply "tears down" the current execution scope as the exception propagates up the stack of exception scopes. This is also referred to as "unwinding" the stack.
Example 5-6 illustrates how a program raises an exception.
Example 5-6 Raising an 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 */ } |
Threads Library 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 either explicitly or implicitly
matches 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 exceptions 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 exception handler
takes appropriate actions in response 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 with the handler 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 exceptions package,
exception handlers are attached, which means that the handler
code appears within the same routine where the specified exceptions are
raised (directly or indirectly). This allows the code to access local
commands when an exception occurs with that exception scope, and allows
the error handling code to be positioned "close" to the code
with which it is associated for readability and maintainability.
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 a matching exception propagates within the execution scope of the associated exception scope.
Use the exceptions package's CATCH macro to define an exception handler code block that is invoked when an exception matching the macro's specified exception object is propagated within the associated exception scope. Use the exceptions package's CATCH_ALL macro to define an exception handler code block that is invoked when any other exception is propagated within the associated exception scope.
An exception handler's code can reraise an exception. That is, the code can propagate an exception to the next outer exception scope for further processing. Use the exceptions package's RERAISE macro to do so. If appropriate, a handler may instead use the RAISE macro to raise a different exception.
Another form of exception handler code is finalization code, or epilogue code. You can define a block of epilogue code and associate it with an exception scope. When an exception is raised, epilogue code performs your cleanup actions within the current exception scope (such as releasing resources), then automatically propagates 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 exceptions package's FINALLY macro to define an epilogue code block. Note that, for a given exception scope, FINALLY blocks and CATCH and CATCH_ALL blocks are mutually exclusive.
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 particular exception by specifying a corresponding exception object 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 a matching exception object. If there is more than one CATCH code block that specifies a matching object within a single TRY/ENDTRY scope, only the first one gains control. (Thus, there is no point in having two CATCH blocks with matching or equivalent exceptions.)
To catch an address exception, the CATCH macro must specify the name of the exception object used in the invoked RAISE macro. 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, read aborted\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 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 execution within the associated exception scope.
If it is appropriate for the caught exception to be propagated to the
next higher exception scope, the
CATCH
code block can use the
RERAISE
macro as its last action 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 that are not being uniquely handled on a per-exception basis in the 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 |
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. This allows the next outer exception scope to handle the exception as it finds appropriate. Invoking the RERAISE macro is valid only within 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 or sufficient reason to attempt to recover fully. For example, a CATCH_ALL code block should always reraise the caught exception as its last action, because the exception handler cannot recover fully from the error since it does not know what the error specifically was.
Refer to Example 5-8 for an example of how a program invokes the
RERAISE
macro as the last action in a
CATCH_ALL
code block.
5.7.6 Defining Epilogue Actions
Some of your program's CATCH or CATCH_ALL code blocks may catch exceptions only for the purpose of performing cleanup actions, such as releasing resources. In many cases, these actions are performed when the TRY code block exits normally or after an exception has been caught. This requires duplicating code in the CATCH_ALL code block and following the exception scope (for the case when an exception does not occur).
The exceptions 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, when no exception is raised), so that they need not be coded more than once.
Example 5-9 demonstrates the FINALLY macro.
Example 5-9 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 pthread_cancel_e exception would propagate out of the pthread_cond_wait() call. The operations in the FINALLY code block release the mutex, after ensuring that the shared data associated with the lock is correct for the next thread that acquires the mutex.
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. |
Previous | Next | Contents | Index |