/*
* Copyright (c) 2002-2008 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#ifdef AG_THREADS
#define _XOPEN_SOURCE 500 /* Require recursive mutexes */
#include
#include
#undef _XOPEN_SOURCE
#include "rwlock.h"
#include
static rwlockattr_t default_rwlockattr = {
0, RWLOCKATTR_MAGIC
};
int
rwlockattr_init(rwlockattr_t *attr)
{
attr->type = 0;
attr->magic = RWLOCKATTR_MAGIC;
return (0);
}
int
rwlockattr_destroy(rwlockattr_t *attr)
{
#ifdef AG_DEBUG
if (attr->magic != RWLOCKATTR_MAGIC)
return (EINVAL);
#endif
attr->type = 0;
attr->magic = 0;
return (0);
}
int
rwlockattr_settype(rwlockattr_t *attr, int type)
{
#ifdef AG_DEBUG
if (attr->magic != RWLOCKATTR_MAGIC)
return (EINVAL);
#endif
attr->type = type;
return (0);
}
int
rwlockattr_gettype(rwlockattr_t *attr, int *type)
{
#ifdef AG_DEBUG
if (attr->magic != RWLOCKATTR_MAGIC)
return (EINVAL);
#endif
*type = attr->type;
return (0);
}
int
rwlock_init(rwlock_t *rw, rwlockattr_t *attr)
{
int res;
rw->attr = (attr == NULL) ? &default_rwlockattr : attr;
res = pthread_mutexattr_init(&rw->mutexattr);
if (res != 0) {
return (res);
}
if (rw->attr->type == RWLOCK_RECURSIVE) {
res = pthread_mutexattr_settype(&rw->mutexattr,
PTHREAD_MUTEX_RECURSIVE);
if (res != 0) {
pthread_mutexattr_destroy(&rw->mutexattr);
return (res);
}
}
res = pthread_mutex_init(&rw->mutex, &rw->mutexattr);
if (res != 0) {
pthread_mutexattr_destroy(&rw->mutexattr);
return (res);
}
res = pthread_cond_init(&rw->condreaders, NULL);
if (res != 0) {
pthread_mutexattr_destroy(&rw->mutexattr);
pthread_mutex_destroy(&rw->mutex);
return (res);
}
res = pthread_cond_init(&rw->condwriters, NULL);
if (res != 0) {
pthread_mutexattr_destroy(&rw->mutexattr);
pthread_mutex_destroy(&rw->mutex);
pthread_cond_destroy(&rw->condreaders);
return (res);
}
rw->nwaitreaders = 0;
rw->nwaitwriters = 0;
rw->ref_count = 0;
rw->magic = RWLOCK_MAGIC;
return (0);
}
int
rwlock_destroy(rwlock_t *rw)
{
#ifdef AG_DEBUG
if (rw->magic != RWLOCK_MAGIC)
return (EINVAL);
#endif
if (rw->ref_count != 0 ||
rw->nwaitreaders != 0 || rw->nwaitwriters != 0) {
return (EBUSY);
}
pthread_mutex_destroy(&rw->mutex);
pthread_cond_destroy(&rw->condreaders);
pthread_cond_destroy(&rw->condwriters);
rw->magic = 0;
return (0);
}
static void
rwlock_cancel_rdwait(void *p)
{
rwlock_t *rw = p;
rw->nwaitreaders--;
pthread_mutex_unlock(&rw->mutex);
}
static void
rwlock_cancel_wrwait(void *p)
{
rwlock_t *rw = p;
rw->nwaitwriters--;
pthread_mutex_unlock(&rw->mutex);
}
int
rwlock_rdlock(rwlock_t *rw)
{
int res;
#ifdef AG_DEBUG
if (rw->magic != RWLOCK_MAGIC)
return (EINVAL);
#endif
if ((res = pthread_mutex_lock(&rw->mutex)) != 0)
return (res);
/* Give preference to waiting writers. */
while (rw->ref_count < 0 || rw->nwaitwriters > 0) {
rw->nwaitreaders++;
pthread_cleanup_push(rwlock_cancel_rdwait, (void *)rw);
res = pthread_cond_wait(&rw->condreaders, &rw->mutex);
pthread_cleanup_pop(0);
rw->nwaitreaders--;
if (res != 0)
break;
}
if (res == 0) {
/* Another reader has a read lock. */
rw->ref_count++;
}
pthread_mutex_unlock(&rw->mutex);
return (res);
}
int
rwlock_tryrdlock(rwlock_t *rw)
{
int res;
#ifdef AG_DEBUG
if (rw->magic != RWLOCK_MAGIC)
return (EINVAL);
#endif
if ((res = pthread_mutex_lock(&rw->mutex)) != 0)
return (res);
if (rw->ref_count < 0 || rw->nwaitwriters > 0) {
/* Held by a writer or waiting writers */
res = EBUSY;
} else {
/* Increment count of reader locks. */
rw->ref_count++;
}
pthread_mutex_unlock(&rw->mutex);
return (res);
}
int
rwlock_wrlock(rwlock_t *rw)
{
int res;
#ifdef AG_DEBUG
if (rw->magic != RWLOCK_MAGIC)
return (EINVAL);
#endif
if ((res = pthread_mutex_lock(&rw->mutex)) != 0)
return (res);
while (rw->ref_count != 0) {
rw->nwaitwriters++;
pthread_cleanup_push(rwlock_cancel_wrwait, (void *)rw);
res = pthread_cond_wait(&rw->condwriters, &rw->mutex);
pthread_cleanup_pop(0);
rw->nwaitwriters--;
if (res != 0)
break;
}
if (res == 0)
rw->ref_count = -1;
pthread_mutex_unlock(&rw->mutex);
return (res);
}
int
rwlock_trywrlock(rwlock_t *rw)
{
int res;
#ifdef AG_DEBUG
if (rw->magic != RWLOCK_MAGIC)
return (EINVAL);
#endif
if ((res = pthread_mutex_lock(&rw->mutex)) != 0)
return (res);
if (rw->ref_count != 0) {
/* Held by either writer or reader(s) */
res = EBUSY;
} else {
/* Available, indicate a writer has it */
rw->ref_count = -1;
}
return (res);
}
int
rwlock_unlock(rwlock_t *rw)
{
int res;
#ifdef AG_DEBUG
if (rw->magic != RWLOCK_MAGIC)
return (EINVAL);
#endif
if ((res = pthread_mutex_lock(&rw->mutex)) != 0)
return (res);
if (rw->ref_count > 0) {
/* Releasing a reader */
rw->ref_count--;
} else if (rw->ref_count == -1) {
/* Releasing a writer */
rw->ref_count = 0;
} else {
return (EINVAL);
}
/* Give preference to waiting writers over waiting readers. */
if (rw->nwaitwriters > 0) {
if (rw->ref_count == 0) {
res = pthread_cond_signal(&rw->condwriters);
}
} else if (rw->nwaitreaders > 0) {
res = pthread_cond_broadcast(&rw->condreaders);
}
pthread_mutex_unlock(&rw->mutex);
return (res);
}
#endif /* AG_THREADS */