The following example defines an exception-handling region without indicating any recovery actions:
TRY { read_tape (); } ENDTRY
The exception scope can express interest in any number of specific exceptions by naming them in CATCH expressions. When an exception reaches the exception scope, control is transferred to the first CATCH clause in the block that matches the exception. If there is more than one CATCH for a given exception within the scope of a single TRY/ENDTRY scope, then only the first one matching the current exception gains control.
To catch an address exception, the CATCH macro must specify the name of the exception object as used in a 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. In general, your program should RAISE and CATCH using the same exception object, even when using status exceptions.
The following example demonstrates catching a specific exception and specifying the recovery action (in this case, a message). After catching the exception and executing the recovery action, the exception is explicitly reraised (causing it to propagate to its callers):
TRY { read_tape (); } CATCH (parity_error) { printf ("Oops, parity error, program terminating\n"); printf ("Try cleaning the heads!\n"); RERAISE; } ENDTRY
The exception scope can express interest in all exceptions using the CATCH_ALL macro. No CATCH macros can follow the CATCH_ALL macro within an exception scope.
Your program should reraise any exception that is caught using a CATCH_ALL macro. It is inappropriate to absorb exceptions 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 clause should explicitly reraise all exceptions; this allows an outer scope to catch this specific exception and perform the appropriate recovery.
The following example shows a CATCH_ALL macro.
int *local_mem; local_mem = malloc (sizeof (int)); TRY { operation(local_mem); /* May raise an exception */ free (local_mem); } CATCH (an_error) { printf ("Oops; caught one!\n"); free (local_mem); RERAISE; } CATCH_ALL { free (local_mem); RERAISE; } ENDTRY
Within the code block of a CATCH or CATCH_ALL macro, you can use the RERAISE function to allow outer exception scopes the chance to handle the exception. Do this when the current scope needs to restore some permanent state (for example, releasing resources such as memory or a mutex) but does not have enough context about the error to attempt to recover.
The RERAISE function is valid only in the code of a CATCH or CATCH_ALL clause. For example:
int *local_mem; local_mem = malloc (sizeof (int)); TRY { operation(local_mem); /* May raise an exception */ free (local_mem); } CATCH (an_error) { free (local_mem); RERAISE; }
Frequently, a block of code catches exceptions only to perform cleanup actions, such as releasing resources. In many cases, the same operations are performed whether the block exits normally or with an exception. Under many exception models, this requires duplicating code, both within a CATCH_ALL type construct and following the exception scope (in case an exception does not occur).
The FINALLY macro catches an exception and then implicitly reraises the exception for outer scopes to handle. The actions defined by a FINALLY clause are also performed when the scope exits normally without an exception, so that they do not need to be duplicated.
Do not combine the FINALLY clause with CATCH or CATCH_ALL. Doing so results in unpredictable behavior.
Following is an example of the FINALLY macro:
int *local_mem; local_mem = malloc (sizeof (int)); TRY { operation(local_mem); /* May raise an exception */ } FINALLY { free (local_mem); } ENDTRY
The current exception object can be referenced within a CATCH or CATCH_ALL block by using the name THIS_CATCH.
The exception object THIS_CATCH has a type of EXCEPTION*. This value can be passed to pthread_exc_get_status_np(), pthread_exc_report_np(), or pthread_exc_matches_np() (defined in Section 5.2.10, Section 5.2.11, and Section 5.2.12).
Because of the way exceptions are propagated, the address contained in THIS_CATCH might not be the actual address of an address exception. To match THIS_CATCH against known exceptions, use pthread_exc_matches_np().
The pthread_exc_set_status_np() function can be used to create a status exception. The exception object must already have been initialized with EXCEPTION_INIT. Any system-specific status value may be used. All exception objects set to the same status value are considered equivalent by the exception facility.
This example demonstrates importing an error status into an exception:
void pthread_exc_set_status_np (EXCEPTION *exception, unsigned int code); static EXCEPTION an_error; EXCEPTION_INIT (an_error); pthread_exc_set_status_np (&an_error, ENOMEM);
Note
On OpenVMS systems, DECthreads exception status values must always have a SEVERE severity level. If necessary, the pthread_exc_set_status_np() operation will modify the severity level of the status code.
The pthread_exc_get_status_np() function can be used to retrieve the system status value from a status exception, for example, after an exception is caught. If the exception object specified is a status exception, pthread_exc_get_status_np() sets the status value argument and returns 0; otherwise, it returns [EINVAL] and does not set the status value argument.
DECthreads reports an exception only when it is raised without a CATCH or CATCH_ALL, immediately before the process is terminated. However, client code might prefer to report an exception as part of error recovery. The pthread_exc_report_np() function prints a message to stderr (on Digital UNIX) or SYS$ERROR (on OpenVMS) describing the exception.
All predefined exceptions have an associated message describing the error. Normally, when the DECthreads exception package has been well integrated with a host platform's status mechanism, external status values can also be reported. However, when an address exception is reported, DECthreads can only report the fact that an exception has occurred and the address of the exception object.
Following is an example of reporting an error:
void pthread_exc_report_np (EXCEPTION *exception); . . . pthread_exc_report_np (&illinstr); . . .
The pthread_exc_matches_np() function compares two exception objects, taking into consideration whether they are address or status exceptions, and possibly other system-specific rules for matching status values. Whenever you need to compare two exceptions, you should use this function. For example:
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); . . . if (pthread_exc_matches_np (THIS_CATCH, &my_status)) fprintf (stderr, "This is my exception\n");
The following example shows the syntax for handling exceptions:
TRY try_block [CATCH (exception_name) handler_block]... [CATCH_ALL handler_block] ENDTRY
A try_block or a handler_block is a sequence of statements, the first of which may be declarations, as in a normal block. If an exception is raised in the try_block, the catch clauses are evaluated to see if any one matches the current exception.
The CATCH or CATCH_ALL clauses absorb an exception---they can catch an exception propagating out of the try_block and direct execution into the associated handler_block. By default, propagation of the exception then ends. Within the lexical scope of a handler, you can either cause propagation of the same exception to resume (called reraising the exception) or raise a new exception.
The RERAISE statement is allowed in any handler statements and causes the current exception to be reraised. Propagation of the caught exception resumes.
The RAISE statement is allowed anywhere and causes a specific exception to start propagating. For example:
TRY sort(); /* Call a function that may raise an exception. * An exception propagates by transferring control * out of some nested routine back to the TRY * clause. Any output parameters or return values * of the called routine are therefore indeterminate. */ CATCH (pthread_cancel_e) printf("Canceled while sorting\n"); RERAISE; CATCH_ALL printf("Some other exception while sorting\n"); RERAISE; ENDTRY
In the preceding example, if the pthread_cancel_e exception propagates out of the function call, the first printf() is executed. If any other exception propagates out of sort, the second printf() is executed. In either situation, propagation of the exception resumes because of the RERAISE statement. (If the code is unable to fully recover from the error or does not understand the error, it needs to further propagate the error to its callers, as in the preceding example.)
The following example shows the syntax for an epilogue:
TRY try_block FINALLY final_block ENDTRY
The final_block is executed regardless of whether the try_block executes to completion without raising an exception or if an exception is raised in the try_block. If an exception is raised in the try_block, propagation of the exception is resumed after executing the final_block.
Note that a CATCH_ALL handler and RERAISE could be used to do this, but the epilogue code would then have to be duplicated in two places, as follows:
TRY try_block CATCH_ALL final_block RERAISE; ENDTRY { final_block }
A FINALLY statement has exactly this meaning but avoids code duplication.
Note
The behavior of FINALLY along with CATCH or CATCH_ALL clauses is unpredictable. Do not combine them for the same try_block.
Another example of the FINALLY statement is as follows:
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); /* 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 the previous example, if the thread was canceled while it was waiting, the call to pthread_cond_wait() could raise the pthread_cancel_e exception. The final_block ensures that the shared data associated with the lock is correct for the next thread that acquires the mutex.
The following rules ensure that exceptions are used in a modular way (so that independent software components can be written without requiring knowledge of each other):
<facility-prefix>_<error-name>_e
TRY handle = open_file (file_name); /* Statements that may raise an exception here */ FINALLY close (handle); ENDTRY
handle = open_file (file_name); TRY /* Statements that may raise an exception here */ FINALLY close (handle); ENDTRY
Table 5-1 lists the DECthreads exceptions and briefly explains the meaning of each exception.
Exception names beginning with the prefix pthread_ are raised as the result of something happening internal to the DECthreads facility and are not meant to be raised by user code. Exceptions beginning with pthread_exc_ are generic and belong to the exception facility or the underlying system.
Exception | Definition |
---|---|
pthread_exc_aritherr_e | Unhandled floating-point exception signal ("arithmetic error") |
pthread_cancel_e | Thread cancelation in progress |
pthread_exc_excpu_e | "cpu-time limit exceeded" |
pthread_exit_e | Thread exited using pthread_exit_e |
pthread_exc_exfilsiz_e | "File size limit exceeded" |
pthread_stackovf_e | Attempted stack overflow was detected |
pthread_exc_decovf_e | Unhandled decimal overflow trap exception |
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 |
This chapter presents an example that shows the use of routines in the DECthreads pthread interface from a C language program. Example 6-1 uses the default pthread status-returning interface to perform a prime number search.
Example 6-1 shows the use of the DECthreads pthread 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 a number (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 workers to be used and the requested number of prime numbers to be found are defined constants. A macro is used to check for error status and to print a given string and the associated error value. Data to be accessed by all threads (mutexes, condition variables, and so on) are declared as global items.
Worker threads execute the prime search routine, which begins by synchronizing with the parent thread using a predicate and a condition variable. A predicate loop encloses the condition wait, to prevent a 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, the workers begin finding prime numbers until canceled by a fellow worker who has found the last requested prime number. Upon each iteration the workers increment the current number to be worked on and take the new value as their work item. A mutex is locked and unlocked around getting the next work item, in order 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.
Each worker thread then determines if its current work item (number) is prime by trying to divide numbers into it. If the number proves to be nondivisible, it is put on the list of primes. Cancels are turned off while working with the list of primes to better control any cancels that do occur. The list of primes and its current count are protected by locks, which also protect the cancelation process of all other worker threads upon finding the last requested prime. While still under the prime list lock, the current worker checks to see if it has found the last requested prime, and, if so, unsets a predicate and cancels all other worker threads. Cancels are then reenabled. 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 pthread 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) perror (string) /* * Global data */ pthread_mutex_t prime_list; /* Mutex for use in accessing the prime */ pthread_mutex_t current_mutex; /* Mutex associated with current number */ pthread_mutex_t cond_mutex; /* Mutex used for ensuring CV integrity */ pthread_cond_t cond_var; /* 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 */ int orig_c_s; /* Original cancel state */ int work_c_s; /* Working cancel state */ int s; /* Cancel routine status */ static void unlock_cond (void *arg) { int status; /* Hold status from pthread calls */ status = pthread_mutex_unlock (&cond_mutex); check(status,"3:Mutex_unlock bad status\n"); } /* * 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. * */ static void * prime_search (void *arg) { div_t div_results; /* DIV results: quot and rem */ int numerator; /* Used for determining primeness */ int denominator; /* Used for determining 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 */ my_number = (int)arg; /* * Synchronize threads and the parent using a condition variable, of * which the predicate (thread_hold) will be set by the parent. */ status = pthread_mutex_lock (&cond_mutex); check(status,"1:Mutex_lock bad status\n"); pthread_cleanup_push (unlock_cond, NULL); while (thread_hold) { status = pthread_cond_wait (&cond_var, &cond_mutex); check(status,"2:Cond_wait bad status\n"); } pthread_cleanup_pop (1); /* * Perform checks on ever larger integers until the requested * number of primes is found. */ while (not_done) { /* Cancelation point */ pthread_testcancel (); /* Get next integer to be checked */ status = pthread_mutex_lock (¤t_mutex); check(status,"4:Mutex_lock bad status\n"); current_num = current_num + 2; /* Skip even numbers */ numerator = current_num; status = pthread_mutex_unlock (¤t_mutex); check(status,"5:Mutex_unlock bad status\n"); /* Only need to divide in half if 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 */ s = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &orig_c_s); /* * 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,"6:Mutex_lock bad status\n"); 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,"12:Cancel bad status\n"); } } } status = pthread_mutex_unlock (&prime_list); check(status,"13:Mutex_unlock bad status\n"); /* Reenable cancels */ s = pthread_setcancelstate(orig_c_s, &work_c_s); } pthread_testcancel (); } pthread_exit ((void *)my_number); return (void *)0; } 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 not_done; /* Indicates swap made in sort */ /* * Create mutexes */ status = pthread_mutex_init (&prime_list, NULL); check(status,"7:Mutex_init bad status\n"); status = pthread_mutex_init (&cond_mutex, NULL); check(status,"8:Mutex_init bad status\n"); status = pthread_mutex_init (¤t_mutex, NULL); check(status,"9:Mutex_init bad status\n"); /* * Create condition variable */ status = pthread_cond_init (&cond_var, NULL); check(status,"10:Cond_init bad status\n"); /* * 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,"11:Pthread_create bad status\n"); } /* * 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,"12:Mutex_lock bad status\n"); thread_hold = 0; status = pthread_cond_broadcast (&cond_var); status = pthread_mutex_unlock (&cond_mutex); check(status,"13:Mutex_unlock bad status\n"); /* * Join each of the worker threads in order 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,"14:Pthread_join bad status\n"); if (exit_value == (void *)worker_num) printf("Thread terminated normally\n"); /* * Upon normal termination the exit_value is equivalent to * worker_num. */ status = pthread_detach ( threads[worker_num] ); check(status,"15:Pthread_detach bad status\n"); } /* * 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. */ not_done = 1; for (index1 = 1; ((index1 < request) && (not_done)); index1++) { for (index2 = 0; index2 < index1; index2++) { if (primes[index1] < primes[index2]) { temp = primes[index2]; primes[index2] = primes[index1]; primes[index1] = temp; not_done = 0; } } } /* * Print out the list of prime numbers that the worker threads * found. */ printf ("The list of %d primes follows:\n", request); printf("%d",primes[0]); for (list = 1; list < request; list++) { printf (",\t%d", primes[list]); } printf ("\n"); }
Note
The pthread routines described here are based on the final POSIX.1c standard approved by the IEEE. DECthreads users should be aware that applications written using the now obsolete DECthreads d4 interfaces (which correspond to the IEEE POSIX 1003.4a/Draft 4 document) will require significant modifications to upgrade to the pthread interface.
6493P006.HTM OSSG Documentation 22-NOV-1996 13:20:03.99
Copyright © Digital Equipment Corporation 1996. All Rights Reserved.