/*
 * This source code was obtained from 
 *     http://www.awl.com/cseng/titles/0-201-63392-2/
 * and is the source code for the programming examples of the book
 * entitled "Programming With POSIX(r) Threads" by Dave Butenhof 
 * (butenhof@zko.dec.com) (thanks Dave!) The original header (which 
 * contains no copyright) is included below. To honor what we perceive
 * as the intention of the original author, there is no claim of
 * Copyright asserted in this file. We have added the log/CVS control
 * info and this text, but otherwise, the code is unmodified from
 * it's original form.
 *
 * The routines in this file implement a useful barrier construct
 * built atop POSIX condition variables and mutexes.
 *
 * The contributors disclaim any representation of warranty: use this
 * code at your own risk.
 *
 * The Code Donkeys at R3vis Corporation.
 */

/* 
 * $Revision: 1.3 $
 * $Name: OpenRM-1-6-0-2-c $
 * $Log: barrier.c,v $
 * Revision 1.3  2002/08/19 00:23:17  wes
 * Renamed routines and types to avoid name collision with those used
 * inside OpenRM - makes debug possible on Win32.
 *
 * Revision 1.2  2001/03/31 16:55:18  wes
 * Added procmode.h, which defines an RMpipe processing mode used in
 * most demonstration programs. The default processing mode is
 * RM_PIPE_MULTISTAGE_VIEW_PARALLEL.
 *
 * Revision 1.1  2000/12/02 17:22:55  wes
 * Initial entry.
 *
 */
 

/*
 * barrier.c
 *
 * This file implements the "barrier" synchronization construct.
 *
 * A barrier causes threads to wait until a set of threads has
 * all "reached" the barrier. The number of threads required is
 * set when the barrier is initialized, and cannot be changed
 * except by reinitializing.
 *
 * The barrier_init() and barrier_destroy() functions,
 * respectively, allow you to initialize and destroy the
 * barrier.
 *
 * The barrier_wait() function allows a thread to wait for a
 * barrier to be completed. One thread (the one that happens to
 * arrive last) will return from barrier_wait() with the status
 * -1 on success -- others will return with 0. The special
 * status makes it easy for the calling code to cause one thread
 * to do something in a serial region before entering another
 * parallel section of code.
 */
#include <pthread.h>
#include <errno.h>
#include "barrier.h"

/*
 * Initialize a barrier for use.
 */
int my_barrier_init (my_barrier_t *barrier, int count)
{
    int status;

    barrier->threshold = barrier->counter = count;
    barrier->cycle = 0;
    status = pthread_mutex_init (&(barrier->mutex), NULL);
    if (status != 0)
        return status;
    status = pthread_cond_init (&(barrier->cv), NULL);
    if (status != 0) {
        pthread_mutex_destroy (&(barrier->mutex));
        return status;
    }
    barrier->valid = BARRIER_VALID;

    return 0;
}

/*
 * Destroy a barrier when done using it.
 */
int my_barrier_destroy (my_barrier_t *barrier)
{
    int status, status2;

    if (barrier->valid != BARRIER_VALID)
        return EINVAL;

    status = pthread_mutex_lock (&(barrier->mutex));
    if (status != 0)
        return status;

    /*
     * Check whether any threads are known to be waiting; report
     * "BUSY" if so.
     */
    if (barrier->counter != barrier->threshold) {
        pthread_mutex_unlock (&(barrier->mutex));
        return EBUSY;
    }

    barrier->valid = 0;
    status = pthread_mutex_unlock (&(barrier->mutex));
    if (status != 0)
        return status;

    /*
     * If unable to destroy either 1003.1c synchronization
     * object, return the error status.
     */
    status = pthread_mutex_destroy (&(barrier->mutex));
    status2 = pthread_cond_destroy (&(barrier->cv));
    return (status == 0 ? status : status2);
}

/*
 * Wait for all members of a barrier to reach the barrier. When
 * the count (of remaining members) reaches 0, broadcast to wake
 * all threads waiting.
 */
int my_barrier_wait (my_barrier_t *barrier)
{
    int status, cancel, tmp, cycle;

    if (barrier->valid != BARRIER_VALID)
        return EINVAL;

    status = pthread_mutex_lock (&(barrier->mutex));
    if (status != 0)
        return status;

    cycle = barrier->cycle;   /* Remember which cycle we're on */

    if (--barrier->counter == 0) {
        barrier->cycle = !barrier->cycle;
        barrier->counter = barrier->threshold;
        status = pthread_cond_broadcast (&(barrier->cv));
        /*
         * The last thread into the barrier will return status
         * -1 rather than 0, so that it can be used to perform
         * some special serial code following the barrier.
         */
        if (status == 0)
            status = -1;
    } else {
        /*
         * Wait with cancellation disabled, because barrier_wait
         * should not be a cancellation point.
         */
        pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &cancel);

        /*
         * Wait until the barrier's cycle changes, which means
         * that it has been broadcast, and we don't want to wait
         * anymore.
         */
        while (cycle == barrier->cycle) {
            status = pthread_cond_wait (
                    &(barrier->cv), &(barrier->mutex));
            if (status != 0) break;
        }

        pthread_setcancelstate (cancel, &tmp);
    }
    /*
     * Ignore an error in unlocking. It shouldn't happen, and
     * reporting it here would be misleading -- the barrier wait
     * completed, after all, whereas returning, for example,
     * EINVAL would imply the wait had failed. The next attempt
     * to use the barrier *will* return an error, or hang, due
     * to whatever happened to the mutex.
     */
    pthread_mutex_unlock (&(barrier->mutex));
    return status;          /* error, -1 for waker, or 0 */
}
