src/lib/librthread/rthread_rwlock_compat.c

259 lines
5.6 KiB
C

/* $OpenBSD: rthread_rwlock_compat.c,v 1.2 2022/05/14 14:52:20 cheloha Exp $ */
/*
* Copyright (c) 2004,2005 Ted Unangst <tedu@openbsd.org>
* Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* rwlocks
*/
#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 the rwlock is statically initialized, perform the dynamic
* initialization.
*/
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);
/* writers have precedence */
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;
/* gotta block */
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) {
/* timed out, sigh */
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) {
/* dequeue and wake first writer */
TAILQ_REMOVE(&lock->writers, next, waiting);
_spinunlock(&lock->lock);
__thrwakeup(next, 1);
return (0);
}
/* could there have been blocked readers? wake them all */
if (was_writer)
__thrwakeup(lock, 0);
out:
_spinunlock(&lock->lock);
return (0);
}