[BACK]Return to rthread_rwlock.c CVS log [TXT][DIR] Up to [local] / src / lib / librthread

File: [local] / src / lib / librthread / rthread_rwlock.c (download)

Revision 1.13, Sun Mar 3 18:39:10 2019 UTC (5 years, 3 months ago) by visa
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, OPENBSD_6_5_BASE, OPENBSD_6_5, HEAD
Changes since 1.12: +2 -2 lines

Wake all waiters when unlocking an rwlock. This fixes a hang
that could happen if there was more than one writer waiting
for a read-locked rwlock.

Problem found by semarie@.

OK semarie@ tedu@

/*	$OpenBSD: rthread_rwlock.c,v 1.13 2019/03/03 18:39:10 visa Exp $ */
/*
 * Copyright (c) 2019 Martin Pieuchot <mpi@openbsd.org>
 * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
 *
 * 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.
 */

#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include <pthread.h>

#include "rthread.h"
#include "synch.h"

#define UNLOCKED	0
#define MAXREADER	0x7ffffffe
#define WRITER		0x7fffffff
#define WAITING		0x80000000
#define COUNT(v)	((v) & WRITER)

#define SPIN_COUNT	128
#if defined(__i386__) || defined(__amd64__)
#define SPIN_WAIT()	asm volatile("pause": : : "memory")
#else
#define SPIN_WAIT()	do { } while (0)
#endif

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 rwlock;

	rwlock = calloc(1, sizeof(*rwlock));
	if (!rwlock)
		return (errno);

	*lockp = rwlock;

	return (0);
}
DEF_STD(pthread_rwlock_init);

int
pthread_rwlock_destroy(pthread_rwlock_t *lockp)
{
	pthread_rwlock_t rwlock;

	rwlock = *lockp;
	if (rwlock) {
		if (rwlock->value != UNLOCKED) {
#define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
			write(2, MSG, sizeof(MSG) - 1);
#undef MSG
			return (EBUSY);
		}
		free((void *)rwlock);
		*lockp = NULL;
	}

	return (0);
}

static int
_rthread_rwlock_ensure_init(pthread_rwlock_t *rwlockp)
{
	int ret = 0;

	/*
	 * If the rwlock is statically initialized, perform the dynamic
	 * initialization.
	 */
	if (*rwlockp == NULL) {
		_spinlock(&rwlock_init_lock);
		if (*rwlockp == NULL)
			ret = pthread_rwlock_init(rwlockp, NULL);
		_spinunlock(&rwlock_init_lock);
	}
	return (ret);
}

static int
_rthread_rwlock_tryrdlock(pthread_rwlock_t rwlock)
{
	unsigned int val;

	do {
		val = rwlock->value;
		if (COUNT(val) == WRITER)
			return (EBUSY);
		if (COUNT(val) == MAXREADER)
			return (EAGAIN);
	} while (atomic_cas_uint(&rwlock->value, val, val + 1) != val);

	membar_enter_after_atomic();
	return (0);
}

static int
_rthread_rwlock_timedrdlock(pthread_rwlock_t *rwlockp, int trywait,
    const struct timespec *abs, int timed)
{
	pthread_t self = pthread_self();
	pthread_rwlock_t rwlock;
	unsigned int val, new;
	int i, error;

	if ((error = _rthread_rwlock_ensure_init(rwlockp)))
		return (error);

	rwlock = *rwlockp;
	_rthread_debug(5, "%p: rwlock_%srdlock %p (%u)\n", self,
	    (timed ? "timed" : (trywait ? "try" : "")), (void *)rwlock,
	    rwlock->value);

	error = _rthread_rwlock_tryrdlock(rwlock);
	if (error != EBUSY || trywait)
		return (error);

	/* Try hard to not enter the kernel. */
	for (i = 0; i < SPIN_COUNT; i++) {
		val = rwlock->value;
		if (val == UNLOCKED || (val & WAITING))
			break;

		SPIN_WAIT();
	}

	while ((error = _rthread_rwlock_tryrdlock(rwlock)) == EBUSY) {
		val = rwlock->value;
		if (val == UNLOCKED || (COUNT(val)) != WRITER)
			continue;
		new = val | WAITING;
		if (atomic_cas_uint(&rwlock->value, val, new) == val) {
			error = _twait(&rwlock->value, new, CLOCK_REALTIME,
			    abs);
		}
		if (error == ETIMEDOUT)
			break;
	}

	return (error);

}

int
pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlockp)
{
	return (_rthread_rwlock_timedrdlock(rwlockp, 1, NULL, 0));
}

int
pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlockp,
    const struct timespec *abs)
{
	return (_rthread_rwlock_timedrdlock(rwlockp, 0, abs, 1));
}

int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlockp)
{
	return (_rthread_rwlock_timedrdlock(rwlockp, 0, NULL, 0));
}

static int
_rthread_rwlock_tryrwlock(pthread_rwlock_t rwlock)
{
	if (atomic_cas_uint(&rwlock->value, UNLOCKED, WRITER) != UNLOCKED)
		return (EBUSY);

	membar_enter_after_atomic();
	return (0);
}


static int
_rthread_rwlock_timedwrlock(pthread_rwlock_t *rwlockp, int trywait,
    const struct timespec *abs, int timed)
{
	pthread_t self = pthread_self();
	pthread_rwlock_t rwlock;
	unsigned int val, new;
	int i, error;

	if ((error = _rthread_rwlock_ensure_init(rwlockp)))
		return (error);

	rwlock = *rwlockp;
	_rthread_debug(5, "%p: rwlock_%swrlock %p (%u)\n", self,
	    (timed ? "timed" : (trywait ? "try" : "")), (void *)rwlock,
	    rwlock->value);

	error = _rthread_rwlock_tryrwlock(rwlock);
	if (error != EBUSY || trywait)
		return (error);

	/* Try hard to not enter the kernel. */
	for (i = 0; i < SPIN_COUNT; i++) {
		val = rwlock->value;
		if (val == UNLOCKED || (val & WAITING))
			break;

		SPIN_WAIT();
	}

	while ((error = _rthread_rwlock_tryrwlock(rwlock)) == EBUSY) {
		val = rwlock->value;
		if (val == UNLOCKED)
			continue;
		new = val | WAITING;
		if (atomic_cas_uint(&rwlock->value, val, new) == val) {
			error = _twait(&rwlock->value, new, CLOCK_REALTIME,
			    abs);
		}
		if (error == ETIMEDOUT)
			break;
	}

	return (error);
}

int
pthread_rwlock_trywrlock(pthread_rwlock_t *rwlockp)
{
	return (_rthread_rwlock_timedwrlock(rwlockp, 1, NULL, 0));
}

int
pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlockp,
    const struct timespec *abs)
{
	return (_rthread_rwlock_timedwrlock(rwlockp, 0, abs, 1));
}

int
pthread_rwlock_wrlock(pthread_rwlock_t *rwlockp)
{
	return (_rthread_rwlock_timedwrlock(rwlockp, 0, NULL, 0));
}

int
pthread_rwlock_unlock(pthread_rwlock_t *rwlockp)
{
	pthread_t self = pthread_self();
	pthread_rwlock_t rwlock;
	unsigned int val, new;

	rwlock = *rwlockp;
	_rthread_debug(5, "%p: rwlock_unlock %p\n", self, (void *)rwlock);

	membar_exit_before_atomic();
	do {
		val = rwlock->value;
		if (COUNT(val) == WRITER || COUNT(val) == 1)
			new = UNLOCKED;
		else
			new = val - 1;
	} while (atomic_cas_uint(&rwlock->value, val, new) != val);

	if (new == UNLOCKED && (val & WAITING))
		_wake(&rwlock->value, INT_MAX);

	return (0);
}