Guide to DECthreads


Previous | Contents

In this example the FINALLY code block assumes that no exception is raised by calling the open_file() routine. If calling open_file() results in raising an exception, the FINALLY code block's close() operation will use an invalid identifier.

Thus, the code in Example 5-15 should be rewritten as shown in Example 5-16.

Example 5-16 Correct Placement of Statements That Might Raise an Exception


 
   handle = open_file (file_name); 
   TRY { 
 
       /* Statements that might raise an exception here */ 
 
   } 
   FINALLY { 
       close (handle); 
   } 
   ENDTRY 
 

The code that is an opening bracket belongs prior to invoking the TRY macro, and the code that is its matching closing bracket belongs in the FINALLY code block.

5.9.3 Raise Exceptions Prior to Performing Side-Effects

Raise exceptions prior to performing side-effects. That is, write routines that propagate exceptions to their callers, so that the routine does not modify any persistent process state before raising the exception. A matching close() call is required only if the open_file() operation is successful. (If an exception is raised, the caller cannot access the output parameters of the function, because the compiler may not have copied temporary values back to their home locations from registers.)

If the open_file() routine raises an exception, the identifier will not have been written, so this open operation must not require that a corresponding close() routine is called when open_file() raises an exception. This property is also what allows the call to be moved to open_file() prior to invoking the TRY macro.

5.9.4 Distinguish Raising Exceptions From Side-Effects

Do not place a return or goto statement between TRY and ENDTRY. It is invalid to return from, branch from, or leave by other means a TRY, CATCH, CATCH_ALL, or FINALLY block. After a given TRY macro is executed, the DECthreads exception package requires that the corresponding ENDTRY macro is also executed.

5.9.5 Declare Variables Within Handler Code as Volatile

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.

Table 5-1 Names of Exception Objects Defined by DECthreads
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).


Note

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.)


5.12 Host Operating System Dependencies

This section mentions dependences 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 DIGITAL C++, 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:

 
   %CMA-F-EXCCOP, exception raised; VMS condition code follows 
   -SYSTEM-F-ACCVIO, access violation, reason mask=00, virtual 
   address=0000000000000000, PC=000000000002013C, PS=0000001B 
 

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. 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.


Chapter 6
DECthreads Examples

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 (&current_mutex); 
        check (status, "Mutex_lock"); 
        current_num = current_num + 2;              /* Skip even numbers */ 
        numerator = current_num; 
        status = pthread_mutex_unlock (&current_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