#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include "rthread.h"
static _atomic_lock_t rwlock_init_lock = _SPINLOCK_UNLOCKED;
int
pthread_rwlock_init(pthread_rwlock_t *lockp,
const pthread_rwlockattr_t *attrp __unused)
{
pthread_rwlock_t lock;
lock = calloc(1, sizeof(*lock));
if (!lock)
return (errno);
lock->lock = _SPINLOCK_UNLOCKED;
TAILQ_INIT(&lock->writers);
*lockp = lock;
return (0);
}
DEF_STD(pthread_rwlock_init);
int
pthread_rwlock_destroy(pthread_rwlock_t *lockp)
{
pthread_rwlock_t lock;
assert(lockp);
lock = *lockp;
if (lock) {
if (lock->readers || !TAILQ_EMPTY(&lock->writers)) {
#define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
write(2, MSG, sizeof(MSG) - 1);
#undef MSG
return (EBUSY);
}
free(lock);
}
*lockp = NULL;
return (0);
}
static int
_rthread_rwlock_ensure_init(pthread_rwlock_t *lockp)
{
int ret = 0;
if (*lockp == NULL)
{
_spinlock(&rwlock_init_lock);
if (*lockp == NULL)
ret = pthread_rwlock_init(lockp, NULL);
_spinunlock(&rwlock_init_lock);
}
return (ret);
}
static int
_rthread_rwlock_rdlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
int try)
{
pthread_rwlock_t lock;
pthread_t thread = pthread_self();
int error;
if ((error = _rthread_rwlock_ensure_init(lockp)))
return (error);
lock = *lockp;
_rthread_debug(5, "%p: rwlock_rdlock %p\n", (void *)thread,
(void *)lock);
_spinlock(&lock->lock);
if (lock->owner == NULL && TAILQ_EMPTY(&lock->writers))
lock->readers++;
else if (try)
error = EBUSY;
else if (lock->owner == thread)
error = EDEADLK;
else {
do {
if (__thrsleep(lock, CLOCK_REALTIME, abstime,
&lock->lock, NULL) == EWOULDBLOCK)
return (ETIMEDOUT);
_spinlock(&lock->lock);
} while (lock->owner != NULL || !TAILQ_EMPTY(&lock->writers));
lock->readers++;
}
_spinunlock(&lock->lock);
return (error);
}
int
pthread_rwlock_rdlock(pthread_rwlock_t *lockp)
{
return (_rthread_rwlock_rdlock(lockp, NULL, 0));
}
int
pthread_rwlock_tryrdlock(pthread_rwlock_t *lockp)
{
return (_rthread_rwlock_rdlock(lockp, NULL, 1));
}
int
pthread_rwlock_timedrdlock(pthread_rwlock_t *lockp,
const struct timespec *abstime)
{
if (abstime == NULL || !timespecisvalid(abstime))
return (EINVAL);
return (_rthread_rwlock_rdlock(lockp, abstime, 0));
}
static int
_rthread_rwlock_wrlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
int try)
{
pthread_rwlock_t lock;
pthread_t thread = pthread_self();
int error;
if ((error = _rthread_rwlock_ensure_init(lockp)))
return (error);
lock = *lockp;
_rthread_debug(5, "%p: rwlock_timedwrlock %p\n", (void *)thread,
(void *)lock);
_spinlock(&lock->lock);
if (lock->readers == 0 && lock->owner == NULL)
lock->owner = thread;
else if (try)
error = EBUSY;
else if (lock->owner == thread)
error = EDEADLK;
else {
int do_wait;
TAILQ_INSERT_TAIL(&lock->writers, thread, waiting);
do {
do_wait = __thrsleep(thread, CLOCK_REALTIME, abstime,
&lock->lock, NULL) != EWOULDBLOCK;
_spinlock(&lock->lock);
} while (lock->owner != thread && do_wait);
if (lock->owner != thread) {
TAILQ_REMOVE(&lock->writers, thread, waiting);
error = ETIMEDOUT;
}
}
_spinunlock(&lock->lock);
return (error);
}
int
pthread_rwlock_wrlock(pthread_rwlock_t *lockp)
{
return (_rthread_rwlock_wrlock(lockp, NULL, 0));
}
int
pthread_rwlock_trywrlock(pthread_rwlock_t *lockp)
{
return (_rthread_rwlock_wrlock(lockp, NULL, 1));
}
int
pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp,
const struct timespec *abstime)
{
if (abstime == NULL || !timespecisvalid(abstime))
return (EINVAL);
return (_rthread_rwlock_wrlock(lockp, abstime, 0));
}
int
pthread_rwlock_unlock(pthread_rwlock_t *lockp)
{
pthread_rwlock_t lock;
pthread_t thread = pthread_self();
pthread_t next;
int was_writer;
lock = *lockp;
_rthread_debug(5, "%p: rwlock_unlock %p\n", (void *)thread,
(void *)lock);
_spinlock(&lock->lock);
if (lock->owner != NULL) {
assert(lock->owner == thread);
was_writer = 1;
} else {
assert(lock->readers > 0);
lock->readers--;
if (lock->readers > 0)
goto out;
was_writer = 0;
}
lock->owner = next = TAILQ_FIRST(&lock->writers);
if (next != NULL) {
TAILQ_REMOVE(&lock->writers, next, waiting);
_spinunlock(&lock->lock);
__thrwakeup(next, 1);
return (0);
}
if (was_writer)
__thrwakeup(lock, 0);
out:
_spinunlock(&lock->lock);
return (0);
}