Document revision date: 19 July 1999 | |
Previous | Contents | Index |
When declaring certain variables that are used within an exception scope, you must use the ANSI C volatile type attribute. The volatile attribute prohibits the compiler from producing certain optimizations with respect to such variables. This ensures that such a variable's value is meaningful after a DECthreads exception object is raised.
Specifically, use the volatile type attribute for a variable whose value is written after the TRY macro is invoked and before the first CATCH/CATCH_ALL/FINALLY macro is invoked and whose value must be used after an exception is caught within a CATCH/CATCH_ALL/FINALLY block or (if the exception is caught and not reraised) after the ENDTRY macro is invoked.
Example 5-17 demonstrates the significance of using the volatile type qualifier for variables that are referenced within an exception scope.
Example 5-17 Correct Placement of Statements That Might Raise an Exception |
---|
void demonstrate_volatile_in_exception_scope (void ) { int updated_before_try; int updated; static int updated_static; volatile int updated_volatile; updated_before_try = 1; updated = 2; updated_static = 3; updated_volatile = 4; TRY { updated = 6; updated_static = 7; updated_volatile = 8; something_that_might_result_in_an_exception(); } CATCH (fully_handled_exception) { /* Fully handle the exception here. Execute the code after ENDTRY next. */ } CATCH_ALL { /* Values of updated_volatile and updated_before_try are meaningful. Values of updated and updated_static are unspecified! */ if (updated > updated_static) printf ("%d, %d", updated, updated_before_try); if (updated > updated_volatile) printf ("%d, %d", updated, updated_before_try); RERAISE; } ENDTRY /* Regardless of the path to this code, the values of updated_volatile and updated_before_try are meaningful. If this code is reached after the ENDTRY macro is invoked and no exception has been raised, the values of updated and updated_static are meaningful. If this code is reached after the exception fully_handled_exception has been caught, the values of updated and updated_static are unspecified! **The following two statements use invalid references to the variables updated and updated_static.** */ if (updated > updated_static) printf ("%d, %d", updated, updated_before_try); if (updated > updated_volatile) printf ("%d, %d", updated, updated_before_try); } /* end demonstrate_volatile_in_exception_scope() */ |
The code in Example 5-17 demonstrates:
This exception scope includes both CATCH and CATCH_ALL code blocks. The variables updated, updated_static, and updated_volatile are set after the TRY macro is invoked. The value of the variable updated_before_try is set once, before the TRY macro is invoked.
The variables updated, updated_static, updated_volatile, and updated_before_try can also be referenced after an exception is caught: within the CATCH_ALL code block or after the ENDTRY macro is executed. Note that the code following the ENDTRY macro can be executed after the exception named fully_handled_exception is caught and its handler executes or if the exception scope is exited without an exception being raised.
Test your program after compiling it with the "optimize"
compiler option, to ensure that your compiler produces the appropriate
exception handler code.
5.9.6 Reraise Caught Exceptions That Are Not Fully Handled
Reraise exceptions that are not fully handled. That is, reraise any exception that you catch, unless your handler has performed the complete recovery action for the error. This rule permits an unhandled exception to propagate to some final default handler that knows how to recover fully.
A corollary of this rule is that CATCH_ALL handlers must reraise the exceptions they catch because they can catch any exception, including those not explicitly known to your code.
It is important to follow this convention, so that your program does
not absorb a thread cancelation exception or thread-exit request
exception. DECthreads maps these requests into exceptions, so that
exception handler code can have the opportunity to handle all
exceptional conditions---from access violations to thread-exit. In some
applications it is important to be able to catch these to preserve an
external invariant, such as an on-disk database.
5.9.7 Declare All Exception Objects as Static
Ensure that you declare (explicitly or implicitly) all exception
objects as static, regardless of their scope in your program.
5.10 Exceptions Defined by DECthreads
Table 5-1 lists the names of exception objects that are defined by DECthreads and the meaning of each exception.
Exception object names that begin with the prefix pthread_ are raised within the DECthreads runtime environment itself and are not meant to be raised by your program code. Names of exception objects that begin with pthread_exc_ are generic and belong to the DECthreads exception package or the underlying system.
Exception | Definition |
---|---|
pthread_cancel_e | Thread cancelation in progress |
pthread_exc_aritherr_e | Unhandled floating-point exception signal ("arithmetic error") |
pthread_exc_decovf_e | Unhandled decimal overflow trap exception |
pthread_exc_excpu_e | "cpu-time limit exceeded" |
pthread_exc_exfilsiz_e | "File size limit exceeded" |
pthread_exc_exquota_e | Operation failed due to insufficient quota |
pthread_exc_fltdiv_e | Unhandled floating-point/decimal divide by zero trap exception |
pthread_exc_fltovf_e | Unhandled floating-point overflow trap exception |
pthread_exc_fltund_e | Unhandled floating-point underflow trap exception |
pthread_exc_illaddr_e | Data or object could not be referenced |
pthread_exc_illinstr_e | Unhandled illegal instruction signal ("illegal instruction") |
pthread_exc_insfmem_e | Insufficient virtual memory for requested operation |
pthread_exc_intdiv_e | Unhandled integer divide by zero trap exception |
pthread_exc_intovf_e | Unhandled integer overflow trap exception |
pthread_exc_nopriv_e | Insufficient privilege for requested operation |
pthread_exc_privinst_e | Unhandled privileged instruction fault exception |
pthread_exc_resaddr_e | Unhandled reserved addressing fault exception |
pthread_exc_resoper_e | Unhandled reserved operand fault exception |
pthread_exc_SIGABRT_e | Unhandled signal ABRT |
pthread_exc_SIGBUS_e | Unhandled bus error signal |
pthread_exc_SIGEMT_e | Unhandled EMT signal |
pthread_exc_SIGFPE_e | Unhandled floating-point exception signal |
pthread_exc_SIGILL_e | Unhandled illegal instruction signal |
pthread_exc_SIGIOT_e | Unhandled IOT signal |
pthread_exc_SIGPIPE_e | Unhandled broken pipe signal |
pthread_exc_SIGSEGV_e | Unhandled segmentation violation signal |
pthread_exc_SIGSYS_e | Unhandled bad system call signal |
pthread_exc_SIGTRAP_e | Unhandled trace or breakpoint trap signal |
pthread_exc_subrng_e | Unhandled subscript out of range exception |
pthread_exc_uninitexc_e | Uninitialized exception raised |
pthread_exit_e | Thread exited using pthread_exit_e |
pthread_stackovf_e | Attempted stack overflow was detected |
5.11 Interoperability of Language-Specific Exceptions
In general, the parts of your program that are coded in a given
language (C, C++, Ada) can use only that language's own exception
objects. This is also true for a program that uses DECthreads.
For example, your program cannot use the DECthreads CATCH to catch a C++ or Ada exception, and cannot use a C++ catch or an Ada except to catch an exception that the program defines using DECthreads.
However, in a program that uses DECthreads, C++ object destructors (as well as the DECthreads FINALLY facility and the equivalent functionality in Ada) will run when an exception from any facility, including DECthreads, reaches that frame. This includes the DECthreads exceptions pthread_cancel_e (cancelation of thread) and pthread_exit_e (thread exit).
On DIGITAL UNIX systems: Prior to DIGITAL UNIX Version 4.0, DECthreads exceptions could not fully interroperate with the C++ and Ada native language exception-handling facilities. For example, raising a DECthreads exception could not trigger invocation of C++ object destructors, and a C++ exception could not be caught by the DECthreads "last chance" exception handler for the calling thread. As a result, a C++ try/catch block could not catch the DECthreads exceptions that indicate cancelation of a thread or thread exit. (However, once caught, the program's DECthreads exception handler can raise the exception again as a C++ exception, which in turn triggers the proper C++ actions to take place.) |
This section mentions dependencies of the DECthreads exception package
on the operating system environment.
5.12.1 No DIGITAL UNIX Dependencies
DIGITAL UNIX has an architecturally specified exception model that is
used by DECthreads as well as C++, DEC Ada, and other languages that
support exceptions. The DEC C compiler has extensions that allow
"native" exception handling.
5.12.2 OpenVMS Conditions and DECthreads Exceptions
On OpenVMS, DECthreads propagates exceptions within the context of the OpenVMS Condition Handling Facility (CHF). An exception is typically raised by calling LIB$STOP with one of the condition codes listed in Table B-3.
Like the pthread pthread_cleanup_push() routine, the DECthreads exception package's CATCH and FINALLY macros establish an OpenVMS condition handler that catches conditions of "fatal" or "severe" severity. Conditions with other severity values are passed through and thus cannot be caught using DECthreads exception handler code.
This requirement also pertains to DECthreads status exceptions. Thus, you cannot use the DECthreads exception package's CATCH, CATCH_ALL, and FINALLY macros to handle a status exception that is not of "severe" or "fatal" severity.
When an exception is raised, your program believes that a OpenVMS condition has been signalled. Until the exception is actually caught (that is, before passing through any TRY blocks or DECthreads cleanup handlers), the primary condition code is either CMA$_EXCEPTION (for an address exception) or a status value (for a status exception).
When a status exception is reraised, whether performed explicitly in a CATCH block or implicitly at the end of a FINALLY block or a DECthreads cleanup handler, DECthreads changes the primary condition code to either CMA$_EXCCOP or CMA$_EXCCOPLOS (depending on whether the contents of the exception can be reliably copied) and chains the original status code to the new primary as a secondary condition code. DECthreads propagates the exception by calling LIB$STOP with the new argument array.
When a status exception is reraised, DECthreads changes the primary condition code to indicate, first, that the exception has been reraised and, second, that the state of the program has been altered since the original exception was raised---that is, some number of frames have been unwound from the stack, which makes unavailable the values of any local variables.
This behavior also has these effects:
For example, output of the following form indicates that some thread incurred an access violation which was propagated as a DECthreads exception without being fully handled, which caused DECthreads to terminate the process.
%CMA-F-EXCCOP, exception raised; VMS condition code follows -SYSTEM-F-ACCVIO, access violation, reason mask=00, virtual address=0000000000000000, PC=000000000002013C, PS=0000001B |
After noticing the location where the access violation occurred, or by running the failing program under the debugger with a breakpoint set on exceptions, you can determine where the exception (in this example, the ACCVIO condition) is originating.
This chapter presents two example programs that use routines in the DECthreads pthread interface. Example 6-1 utilizes one parent thread and a set of worker threads to perform a prime number search. Example 6-2 implements a simple, text-based, asynchronous user interface that reads and writes commands to the terminal.
Both examples use the pthread interface routines and
rely upon their default status-returning mechanism to indicate routine
completion status. Example 6-1 uses the POSIX cleanup handler
mechanism to cleanup from thread cancelation. In contrast,
Example 6-2 uses the DECthreads exception package to capture and
cleanup from thread cancelation and other synchronous fatal error
conditions.
6.1 Prime Number Search Example
Example 6-1 shows the use of DECthreads pthread interface routines in a C program that performs a prime number search. The program finds a specified number of prime numbers, then sorts and displays these numbers. Several threads participate in the search: each thread takes a number (the next one to be checked), checks whether it is a prime, records it if it is prime, and then takes another number, and so on.
This program reflects the work crew functional model (see Section 1.4.2.) The worker threads increment the integer variable current_num to get their next work assignment. As a whole, the worker threads are responsible for finding a specified number of prime numbers, at which point their work is complete.
The number of worker threads to use and the number of prime numbers to find are defined as constants. A macro checks for an error status from each call to DECthreads and prints a given string and the associated error value. Data that is accessed by all threads (mutexes, condition variables, and so on) are declared as global items.
Each worker thread executes the prime_search() routine, which immediately waits for permission to continue from the parent thread. The worker thread synchronizes with the parent thread using a predicate and a condition variable. Before and after waiting on the condition variable, each worker thread pushes and pops, respectively, a cleanup handler routine (unlock_cond() to allow recovery from cancelation or other unexpected thread exit.
Notice that a predicate loop encloses the condition wait, to prevent the worker thread from continuing if it is wrongly signaled or broadcasted. The lock associated with the condition variable must be held by the thread during the call to condition wait. The lock is released within the call and acquired again upon being signaled or broadcasted. Note that the same mutex must be used for all operations performed on a specific condition variable.
After the parent sets the predicate and broadcasts, each worker thread begins finding prime numbers until canceled by a fellow worker who has found the last requested prime number. Upon each iteration a given worker increments the current number to examine and takes that new value as its next work item. Each worker thread uses a mutex to access the next work item, to ensure that no two threads are working on the same item. This type of locking protocol should be performed on all global data to ensure its integrity.
Next, each worker thread determines whether its current work item is prime by trying to divide numbers into it. If the number proves to be nondivisible, it is put on the list of primes. The worker thread disables its own cancelability while working with the list of primes, better to control any cancelation requests that might occur. The list of primes and its current count are protected by mutexes, which also protect the step of canceling all other worker threads upon finding the last requested prime. While the prime list mutex's remains locked, the worker checks whether it has found the last requested prime, and, if so, unsets a predicate and cancels all other worker threads. Finally, the worker enables its own cancelability.
The canceling thread should fall out of the work loop as a result of the predicate that it unsets.
The parent thread's flow of execution is as follows:
The following DECthreads pthread interface routines are used in Example 6-1:
Example 6-1 C Program Example (Prime Number Search) |
---|
/* * * DECthreads example program conducting a prime number search * */ #include <pthread.h> #include <stdio.h> #include <stdlib.h> /* * Constants used by the example. */ #define workers 5 /* Threads to perform prime check */ #define request 110 /* Number of primes to find */ /* * Macros */ #define check(status,string) if (status != 0) { \ errno = status; \ fprintf (stderr, "%s status %d: %s\n", status, string, strerror (status)); \ } /* * Global data */ pthread_mutex_t prime_list = PTHREAD_MUTEX_INITIALIZER; /* Mutex for use in accessing the prime */ pthread_mutex_t current_mutex = PTHREAD_MUTEX_INITIALIZER; /* Mutex associated with current number */ pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER; /* Mutex used for thread start */ pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER; /* Condition variable for thread start */ int current_num= -1;/* Next number to be checked, start odd */ int thread_hold=1; /* Number associated with condition state */ int count=0; /* Count of prime numbers - index to primes */ int primes[request];/* Store prime numbers - synchronize access */ pthread_t threads[workers]; /* Array of worker threads */ static void unlock_cond (void* arg) { int status; /* Hold status from pthread calls */ status = pthread_mutex_unlock (&cond_mutex); check (status, "Mutex_unlock"); } /* * Worker thread routine. * * Worker threads start with this routine, which begins with a condition wait * designed to synchronize the workers and the parent. Each worker thread then * takes a turn taking a number for which it will determine whether or not it * is prime. */ void * prime_search (void* arg) { int numerator; /* Used to determine primeness */ int denominator; /* Used to determine primeness */ int cut_off; /* Number being checked div 2 */ int notifiee; /* Used during a cancelation */ int prime; /* Flag used to indicate primeness */ int my_number; /* Worker thread identifier */ int status; /* Hold status from pthread calls */ int not_done=1; /* Work loop predicate */ int oldstate; /* Old cancel state */ my_number = (int)arg; /* * Synchronize threads and the parent using a condition variable, the * predicate of which (thread_hold) will be set by the parent. */ status = pthread_mutex_lock (&cond_mutex); check (status, "Mutex_lock"); pthread_cleanup_push (unlock_cond, NULL); while (thread_hold) { status = pthread_cond_wait (&cond_var, &cond_mutex); check (status, "Cond_wait"); } pthread_cleanup_pop (1); /* * Perform checks on ever larger integers until the requested * number of primes is found. */ while (not_done) { /* Test for cancellation request */ pthread_testcancel (); /* Get next integer to be checked */ status = pthread_mutex_lock (¤t_mutex); check (status, "Mutex_lock"); current_num = current_num + 2; /* Skip even numbers */ numerator = current_num; status = pthread_mutex_unlock (¤t_mutex); check (status, "Mutex_unlock"); /* Only need to divide in half of number to verify not prime */ cut_off = numerator/2 + 1; prime = 1; /* Check for prime; exit if something evenly divides */ for (denominator = 2; ((denominator < cut_off) && (prime)); denominator++) { prime = numerator % denominator; } if (prime != 0) { /* Explicitly turn off all cancels */ pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &oldstate); /* * Lock a mutex and add this prime number to the list. Also, * if this fulfills the request, cancel all other threads. */ status = pthread_mutex_lock (&prime_list); check (status, "Mutex_lock"); if (count < request) { primes[count] = numerator; count++; } else if (count >= request) { not_done = 0; count++; for (notifiee = 0; notifiee < workers; notifiee++) { if (notifiee != my_number) { status = pthread_cancel (threads[notifiee]); check (status, "Cancel"); } } } status = pthread_mutex_unlock (&prime_list); check (status, "Mutex_unlock"); /* Reenable cancellation */ pthread_setcancelstate (oldstate, &oldstate); } pthread_testcancel (); } return arg; } main() { int worker_num; /* Counter used when indexing workers */ void *exit_value; /* Individual worker's return status */ int list; /* Used to print list of found primes */ int status; /* Hold status from pthread calls */ int index1; /* Used in sorting prime numbers */ int index2; /* Used in sorting prime numbers */ int temp; /* Used in a swap; part of sort */ int line_idx; /* Column alignment for output */ /* * Create the worker threads. */ for (worker_num = 0; worker_num < workers; worker_num++) { status = pthread_create ( &threads[worker_num], NULL, prime_search, (void*)worker_num); check (status, "Pthread_create"); } /* * Set the predicate thread_hold to zero, and broadcast on the * condition variable that the worker threads may proceed. */ status = pthread_mutex_lock (&cond_mutex); check (status, "Mutex_lock"); thread_hold = 0; status = pthread_cond_broadcast (&cond_var); check (status, "Cond_broadcast"); status = pthread_mutex_unlock (&cond_mutex); check (status, "Mutex_unlock"); /* * Join each of the worker threads inorder to obtain their * summation totals, and to ensure each has completed * successfully. * * Mark thread storage free to be reclaimed upon termination by * detaching it. */ for (worker_num = 0; worker_num < workers; worker_num++) { status = pthread_join (threads[worker_num], &exit_value); check (status, "Pthread_join"); if (exit_value == (void*)worker_num) printf ("Thread %d terminated normally\n", worker_num); else if (exit_value == PTHREAD_CANCELED) printf ("Thread %d was cancelled\n", worker_num); else printf ("Thread %d terminated unexpectedly with %#lx\n", worker_num, exit_value); /* * Upon normal termination the exit_value is equivalent to worker_num. */ } /* * Take the list of prime numbers found by the worker threads and * sort them from lowest value to highest. The worker threads work * concurrently; there is no guarantee that the prime numbers * will be found in order. Therefore, a sort is performed. */ for (index1 = 1; index1 < request; index1++) { for (index2 = 0; index2 < index1; index2++) { if (primes[index1] < primes[index2]) { temp = primes[index2]; primes[index2] = primes[index1]; primes[index1] = temp; } } } /* * Print out the list of prime numbers that the worker threads * found. */ printf ("The list of %d primes follows:\n", request); for (list = 0, line_idx = 0; list < request; list++, line_idx++) { if (line_idx >= 10) { printf (",\n"); line_idx = 0; } else if (line_idx > 0) printf (",\t"); printf ("%d", primes[list]); } printf ("\n"); } |
Previous | Next | Contents | Index |
privacy and legal statement | ||
6101PRO_011.HTML |