Merge pull request #2411 in HDFFV/hdf5 from ~DYOUNG/werror:darwin-barriers to develop

* commit '803d805c74466a9d736455930b17de2d9f5cb02d':
  Complete the comment on thread_main(), explaining why the barrier is used.
  The first implementation seemed to allow for the possibility that a thread could block at the barrier, wake and exit the barrier, re-acquire the barrier lock and increase `nentered` before the other blocked threads woke and checked `nentered % count == 0`.  Then the other blocked threads would check `nentered % count == 0` and, finding it false, go back to sleep in the barrier.  This new implementation waits for a looser condition to obtain so that threads don't go back to sleep in the barrier.
  Test the right condition for the EBUSY return in pthread_barrier_destroy().
  s/exit_failure/EXIT_FAILURE/g
  Implement pthread_barrier(3) for Darwin using a counter, condition variable, and mutex.  Untested.
This commit is contained in:
Larry Knox 2020-02-28 06:22:13 -06:00
commit d7eec7d6ec

View File

@ -42,6 +42,136 @@ my_errx(int code, const char *fmt, ...)
#if defined(H5_HAVE_THREADSAFE) && !defined(H5_HAVE_WIN_THREADS)
#if defined(H5_HAVE_DARWIN)
typedef struct _pthread_barrierattr {
uint8_t unused;
} pthread_barrierattr_t;
typedef struct _pthread_barrier {
uint32_t magic;
unsigned int count;
uint64_t nentered;
pthread_cond_t cv;
pthread_mutex_t mtx;
} pthread_barrier_t;
int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *,
unsigned int);
int pthread_barrier_wait(pthread_barrier_t *);
int pthread_barrier_destroy(pthread_barrier_t *);
static const uint32_t barrier_magic = 0xf00dd00f;
int
pthread_barrier_init(pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr, unsigned int count)
{
int rc;
if (count == 0)
return EINVAL;
if (attr != NULL)
return EINVAL;
memset(barrier, 0, sizeof(*barrier));
barrier->count = count;
if ((rc = pthread_cond_init(&barrier->cv, NULL)) != 0)
return rc;
if ((rc = pthread_mutex_init(&barrier->mtx, NULL)) != 0) {
(void)pthread_cond_destroy(&barrier->cv);
return rc;
}
barrier->magic = barrier_magic;
return 0;
}
static void
barrier_lock(pthread_barrier_t *barrier)
{
int rc;
if ((rc = pthread_mutex_lock(&barrier->mtx)) != 0) {
my_errx(EXIT_FAILURE, "%s: pthread_mutex_lock: %s", __func__,
strerror(rc));
}
}
static void
barrier_unlock(pthread_barrier_t *barrier)
{
int rc;
if ((rc = pthread_mutex_unlock(&barrier->mtx)) != 0) {
my_errx(EXIT_FAILURE, "%s: pthread_mutex_unlock: %s", __func__,
strerror(rc));
}
}
int
pthread_barrier_destroy(pthread_barrier_t *barrier)
{
int rc;
barrier_lock(barrier);
if (barrier->magic != barrier_magic)
rc = EINVAL;
else if (barrier->nentered % barrier->count != 0)
rc = EBUSY;
else {
rc = 0;
barrier->magic = ~barrier->magic;
}
barrier_unlock(barrier);
if (rc != 0)
return rc;
(void)pthread_cond_destroy(&barrier->cv);
(void)pthread_mutex_destroy(&barrier->mtx);
return 0;
}
int
pthread_barrier_wait(pthread_barrier_t *barrier)
{
int rc;
uint64_t threshold;
if (barrier == NULL)
return EINVAL;
barrier_lock(barrier);
if (barrier->magic != barrier_magic) {
rc = EINVAL;
goto out;
}
/* Compute the release `threshold`. All threads entering with count = 5
* and `nentered` in [0, 4] should be released once `nentered` reaches 5:
* call 5 the release `threshold`. All threads entering with count = 5
* and `nentered` in [5, 9] should be released once `nentered` reaches 10.
*/
threshold = (barrier->nentered / barrier->count + 1) * barrier->count;
barrier->nentered++;
while (barrier->nentered < threshold) {
if ((rc = pthread_cond_wait(&barrier->cv, &barrier->mtx)) != 0)
goto out;
}
rc = pthread_cond_broadcast(&barrier->cv);
out:
barrier_unlock(barrier);
return rc;
}
#endif /* H5_HAVE_DARWIN */
static void my_err(int, const char *, ...) H5_ATTR_FORMAT(printf, 2, 3);
static void
@ -96,7 +226,15 @@ atomic_printf(const char *fmt, ...)
/* Each thread runs this routine. The routine fetches the current
* thread's ID, makes sure that it is in the expected range, makes
* sure that in this round of testing, no two threads shared the
* same ID,
* same ID, and checks that each thread's ID is constant over its lifetime.
*
* main() checks that every ID in [1, NTHREADS] is used in each round
* of testing. All NTHREADS threads synchronize on a barrier after each
* has fetched its ID. The barrier guarantees that all threads' lifetimes
* overlap at least momentarily, so the IDs will be unique, and there
* will be NTHREADS of them. Further, since thread IDs are assigned
* starting with 1, and the number of threads with IDs alive never exceeds
* NTHREADS, the least ID has to be 1 and the greatest, NTHREADS.
*/
static void *
thread_main(void H5_ATTR_UNUSED *arg)