#include <pthread.h>
#include "rwl.h"

void
rwl_init(rwl_t *rwlp)
{
	pthread_mutex_init(&rwlp->m, pthread_mutexattr_default);
	pthread_cond_init(&rwlp->readers_ok,  pthread_condattr_default);
	pthread_cond_init(&rwlp->writer_ok,  pthread_condattr_default);
	rwlp->rwlock = 0;
	rwlp->waiting_writers = 0;
}
/*
 * Acquire a read lock. Multiple readers can go if there are no
 * writers.
 */
void
rwl_rdlock(rwl_t *rwlp)
{
	pthread_mutex_lock(&rwlp->m);
	while (rwlp->rwlock < 0 || rwlp->waiting_writers)
		pthread_cond_wait(&rwlp->readers_ok, &rwlp->m);
	rwlp->rwlock++;
	pthread_mutex_unlock(&rwlp->m);
}
/*
 * Acquire a write lock. Only a single writer can proceed.
 */
void
rwl_wrlock(rwl_t *rwlp)
{
	pthread_mutex_lock(&rwlp->m);
	while (rwlp->rwlock != 0) {
		rwlp->waiting_writers++;
		pthread_cond_wait(&rwlp->writer_ok, &rwlp->m);
		rwlp->waiting_writers--;
	}
	rwlp->rwlock = -1;
	pthread_mutex_unlock(&rwlp->m);
}
/*
 * Unlock the read/write lock.
 */
void
rwl_unlock(rwl_t *rwlp)
{
	int ww, wr;

	pthread_mutex_lock(&rwlp->m);
	if (rwlp->rwlock < 0) /* rwlock < 0 if locked for writing */
		rwlp->rwlock = 0;
	else
		rwlp->rwlock--;
	/*
	 * Keep flags that show if there are waiting readers or writers so
	 * that we can wake them up outside the monitor lock.
	 */
	ww = (rwlp->waiting_writers && rwlp->rwlock == 0);
	wr = (rwlp->waiting_writers == 0);
	pthread_mutex_unlock(&rwlp->m);
	/* wakeup a waiting writer first. Otherwise wakeup all readers */
	if (ww)
		pthread_cond_signal(&rwlp->writer_ok);
	else if (wr)
		pthread_cond_broadcast(&rwlp->readers_ok);
}