andre@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
andre@0: /* This Source Code Form is subject to the terms of the Mozilla Public
andre@0:  * License, v. 2.0. If a copy of the MPL was not distributed with this
andre@0:  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
andre@0: 
andre@0: #include "primpl.h"
andre@0: 
andre@0: #include <string.h>
andre@0: 
andre@0: #if defined(HPUX) && defined(_PR_PTHREADS) && !defined(_PR_DCETHREADS)
andre@0: 
andre@0: #include <pthread.h>
andre@0: #define HAVE_UNIX98_RWLOCK
andre@0: #define RWLOCK_T pthread_rwlock_t
andre@0: #define RWLOCK_INIT(lock) pthread_rwlock_init(lock, NULL)
andre@0: #define RWLOCK_DESTROY(lock) pthread_rwlock_destroy(lock)
andre@0: #define RWLOCK_RDLOCK(lock) pthread_rwlock_rdlock(lock)
andre@0: #define RWLOCK_WRLOCK(lock) pthread_rwlock_wrlock(lock)
andre@0: #define RWLOCK_UNLOCK(lock) pthread_rwlock_unlock(lock)
andre@0: 
andre@0: #elif defined(SOLARIS) && (defined(_PR_PTHREADS) \
andre@0:         || defined(_PR_GLOBAL_THREADS_ONLY))
andre@0: 
andre@0: #include <synch.h>
andre@0: #define HAVE_UI_RWLOCK
andre@0: #define RWLOCK_T rwlock_t
andre@0: #define RWLOCK_INIT(lock) rwlock_init(lock, USYNC_THREAD, NULL)
andre@0: #define RWLOCK_DESTROY(lock) rwlock_destroy(lock)
andre@0: #define RWLOCK_RDLOCK(lock) rw_rdlock(lock)
andre@0: #define RWLOCK_WRLOCK(lock) rw_wrlock(lock)
andre@0: #define RWLOCK_UNLOCK(lock) rw_unlock(lock)
andre@0: 
andre@0: #endif
andre@0: 
andre@0: /*
andre@0:  * Reader-writer lock
andre@0:  */
andre@0: struct PRRWLock {
andre@0: 	char			*rw_name;			/* lock name					*/
andre@0: 	PRUint32		rw_rank;			/* rank of the lock				*/
andre@0: 
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	RWLOCK_T		rw_lock;
andre@0: #else
andre@0:     PRLock			*rw_lock;
andre@0: 	PRInt32			rw_lock_cnt;		/* ==  0, if unlocked			*/
andre@0: 										/* == -1, if write-locked		*/
andre@0: 										/* > 0	, # of read locks		*/
andre@0: 	PRUint32		rw_reader_cnt;		/* number of waiting readers	*/
andre@0: 	PRUint32		rw_writer_cnt;		/* number of waiting writers	*/
andre@0: 	PRCondVar   	*rw_reader_waitq;	/* cvar for readers 			*/
andre@0: 	PRCondVar   	*rw_writer_waitq;	/* cvar for writers				*/
andre@0: #ifdef DEBUG
andre@0:     PRThread 		*rw_owner;			/* lock owner for write-lock	*/
andre@0: #endif
andre@0: #endif
andre@0: };
andre@0: 
andre@0: #ifdef DEBUG
andre@0: #define _PR_RWLOCK_RANK_ORDER_DEBUG	/* enable deadlock detection using
andre@0: 									   rank-order for locks
andre@0: 									*/
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 
andre@0: static PRUintn	pr_thread_rwlock_key;			/* TPD key for lock stack */
andre@0: static PRUintn	pr_thread_rwlock_alloc_failed;
andre@0: 
andre@0: #define	_PR_RWLOCK_RANK_ORDER_LIMIT	10
andre@0: 
andre@0: typedef struct thread_rwlock_stack {
andre@0: 	PRInt32		trs_index;									/* top of stack */
andre@0: 	PRRWLock	*trs_stack[_PR_RWLOCK_RANK_ORDER_LIMIT];	/* stack of lock
andre@0: 														 	   pointers */
andre@0: 
andre@0: } thread_rwlock_stack;
andre@0: 
andre@0: static void _PR_SET_THREAD_RWLOCK_RANK(PRRWLock *rwlock);
andre@0: static PRUint32 _PR_GET_THREAD_RWLOCK_RANK(void);
andre@0: static void _PR_UNSET_THREAD_RWLOCK_RANK(PRRWLock *rwlock);
andre@0: static void _PR_RELEASE_LOCK_STACK(void *lock_stack);
andre@0: 
andre@0: #endif
andre@0: 
andre@0: /*
andre@0:  * Reader/Writer Locks
andre@0:  */
andre@0: 
andre@0: /*
andre@0:  * PR_NewRWLock
andre@0:  *		Create a reader-writer lock, with the given lock rank and lock name
andre@0:  *	
andre@0:  */
andre@0: 
andre@0: PR_IMPLEMENT(PRRWLock *)
andre@0: PR_NewRWLock(PRUint32 lock_rank, const char *lock_name)
andre@0: {
andre@0:     PRRWLock *rwlock;
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	int err;
andre@0: #endif
andre@0: 
andre@0:     if (!_pr_initialized) _PR_ImplicitInitialization();
andre@0: 
andre@0:     rwlock = PR_NEWZAP(PRRWLock);
andre@0:     if (rwlock == NULL)
andre@0: 		return NULL;
andre@0: 
andre@0: 	rwlock->rw_rank = lock_rank;
andre@0: 	if (lock_name != NULL) {
andre@0: 		rwlock->rw_name = (char*) PR_Malloc(strlen(lock_name) + 1);
andre@0:     	if (rwlock->rw_name == NULL) {
andre@0: 			PR_DELETE(rwlock);
andre@0: 			return(NULL);
andre@0: 		}
andre@0: 		strcpy(rwlock->rw_name, lock_name);
andre@0: 	} else {
andre@0: 		rwlock->rw_name = NULL;
andre@0: 	}
andre@0: 	
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	err = RWLOCK_INIT(&rwlock->rw_lock);
andre@0: 	if (err != 0) {
andre@0: 		PR_SetError(PR_UNKNOWN_ERROR, err);
andre@0: 		PR_Free(rwlock->rw_name);
andre@0: 		PR_DELETE(rwlock);
andre@0: 		return NULL;
andre@0: 	}
andre@0: 	return rwlock;
andre@0: #else
andre@0: 	rwlock->rw_lock = PR_NewLock();
andre@0:     if (rwlock->rw_lock == NULL) {
andre@0: 		goto failed;
andre@0: 	}
andre@0: 	rwlock->rw_reader_waitq = PR_NewCondVar(rwlock->rw_lock);
andre@0:     if (rwlock->rw_reader_waitq == NULL) {
andre@0: 		goto failed;
andre@0: 	}
andre@0: 	rwlock->rw_writer_waitq = PR_NewCondVar(rwlock->rw_lock);
andre@0:     if (rwlock->rw_writer_waitq == NULL) {
andre@0: 		goto failed;
andre@0: 	}
andre@0: 	rwlock->rw_reader_cnt = 0;
andre@0: 	rwlock->rw_writer_cnt = 0;
andre@0: 	rwlock->rw_lock_cnt = 0;
andre@0: 	return rwlock;
andre@0: 
andre@0: failed:
andre@0: 	if (rwlock->rw_reader_waitq != NULL) {
andre@0: 		PR_DestroyCondVar(rwlock->rw_reader_waitq);	
andre@0: 	}
andre@0: 	if (rwlock->rw_lock != NULL) {
andre@0: 		PR_DestroyLock(rwlock->rw_lock);
andre@0: 	}
andre@0: 	PR_Free(rwlock->rw_name);
andre@0: 	PR_DELETE(rwlock);
andre@0: 	return NULL;
andre@0: #endif
andre@0: }
andre@0: 
andre@0: /*
andre@0: ** Destroy the given RWLock "lock".
andre@0: */
andre@0: PR_IMPLEMENT(void)
andre@0: PR_DestroyRWLock(PRRWLock *rwlock)
andre@0: {
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	int err;
andre@0: 	err = RWLOCK_DESTROY(&rwlock->rw_lock);
andre@0: 	PR_ASSERT(err == 0);
andre@0: #else
andre@0: 	PR_ASSERT(rwlock->rw_reader_cnt == 0);
andre@0: 	PR_DestroyCondVar(rwlock->rw_reader_waitq);	
andre@0: 	PR_DestroyCondVar(rwlock->rw_writer_waitq);	
andre@0: 	PR_DestroyLock(rwlock->rw_lock);
andre@0: #endif
andre@0: 	if (rwlock->rw_name != NULL)
andre@0: 		PR_Free(rwlock->rw_name);
andre@0:     PR_DELETE(rwlock);
andre@0: }
andre@0: 
andre@0: /*
andre@0: ** Read-lock the RWLock.
andre@0: */
andre@0: PR_IMPLEMENT(void)
andre@0: PR_RWLock_Rlock(PRRWLock *rwlock)
andre@0: {
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: int err;
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 	/*
andre@0: 	 * assert that rank ordering is not violated; the rank of 'rwlock' should
andre@0: 	 * be equal to or greater than the highest rank of all the locks held by
andre@0: 	 * the thread.
andre@0: 	 */
andre@0: 	PR_ASSERT((rwlock->rw_rank == PR_RWLOCK_RANK_NONE) || 
andre@0: 					(rwlock->rw_rank >= _PR_GET_THREAD_RWLOCK_RANK()));
andre@0: #endif
andre@0: 
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	err = RWLOCK_RDLOCK(&rwlock->rw_lock);
andre@0: 	PR_ASSERT(err == 0);
andre@0: #else
andre@0: 	PR_Lock(rwlock->rw_lock);
andre@0: 	/*
andre@0: 	 * wait if write-locked or if a writer is waiting; preference for writers
andre@0: 	 */
andre@0: 	while ((rwlock->rw_lock_cnt < 0) ||
andre@0: 			(rwlock->rw_writer_cnt > 0)) {
andre@0: 		rwlock->rw_reader_cnt++;
andre@0: 		PR_WaitCondVar(rwlock->rw_reader_waitq, PR_INTERVAL_NO_TIMEOUT);
andre@0: 		rwlock->rw_reader_cnt--;
andre@0: 	}
andre@0: 	/*
andre@0: 	 * Increment read-lock count
andre@0: 	 */
andre@0: 	rwlock->rw_lock_cnt++;
andre@0: 
andre@0: 	PR_Unlock(rwlock->rw_lock);
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 	/*
andre@0: 	 * update thread's lock rank
andre@0: 	 */
andre@0: 	if (rwlock->rw_rank != PR_RWLOCK_RANK_NONE)
andre@0: 		_PR_SET_THREAD_RWLOCK_RANK(rwlock);
andre@0: #endif
andre@0: }
andre@0: 
andre@0: /*
andre@0: ** Write-lock the RWLock.
andre@0: */
andre@0: PR_IMPLEMENT(void)
andre@0: PR_RWLock_Wlock(PRRWLock *rwlock)
andre@0: {
andre@0: #if defined(DEBUG)
andre@0: PRThread *me = PR_GetCurrentThread();
andre@0: #endif
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: int err;
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 	/*
andre@0: 	 * assert that rank ordering is not violated; the rank of 'rwlock' should
andre@0: 	 * be equal to or greater than the highest rank of all the locks held by
andre@0: 	 * the thread.
andre@0: 	 */
andre@0: 	PR_ASSERT((rwlock->rw_rank == PR_RWLOCK_RANK_NONE) || 
andre@0: 					(rwlock->rw_rank >= _PR_GET_THREAD_RWLOCK_RANK()));
andre@0: #endif
andre@0: 
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	err = RWLOCK_WRLOCK(&rwlock->rw_lock);
andre@0: 	PR_ASSERT(err == 0);
andre@0: #else
andre@0: 	PR_Lock(rwlock->rw_lock);
andre@0: 	/*
andre@0: 	 * wait if read locked
andre@0: 	 */
andre@0: 	while (rwlock->rw_lock_cnt != 0) {
andre@0: 		rwlock->rw_writer_cnt++;
andre@0: 		PR_WaitCondVar(rwlock->rw_writer_waitq, PR_INTERVAL_NO_TIMEOUT);
andre@0: 		rwlock->rw_writer_cnt--;
andre@0: 	}
andre@0: 	/*
andre@0: 	 * apply write lock
andre@0: 	 */
andre@0: 	rwlock->rw_lock_cnt--;
andre@0: 	PR_ASSERT(rwlock->rw_lock_cnt == -1);
andre@0: #ifdef DEBUG
andre@0: 	PR_ASSERT(me != NULL);
andre@0: 	rwlock->rw_owner = me;
andre@0: #endif
andre@0: 	PR_Unlock(rwlock->rw_lock);
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 	/*
andre@0: 	 * update thread's lock rank
andre@0: 	 */
andre@0: 	if (rwlock->rw_rank != PR_RWLOCK_RANK_NONE)
andre@0: 		_PR_SET_THREAD_RWLOCK_RANK(rwlock);
andre@0: #endif
andre@0: }
andre@0: 
andre@0: /*
andre@0: ** Unlock the RW lock.
andre@0: */
andre@0: PR_IMPLEMENT(void)
andre@0: PR_RWLock_Unlock(PRRWLock *rwlock)
andre@0: {
andre@0: #if defined(DEBUG)
andre@0: PRThread *me = PR_GetCurrentThread();
andre@0: #endif
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: int err;
andre@0: #endif
andre@0: 
andre@0: #if defined(HAVE_UNIX98_RWLOCK) || defined(HAVE_UI_RWLOCK)
andre@0: 	err = RWLOCK_UNLOCK(&rwlock->rw_lock);
andre@0: 	PR_ASSERT(err == 0);
andre@0: #else
andre@0: 	PR_Lock(rwlock->rw_lock);
andre@0: 	/*
andre@0: 	 * lock must be read or write-locked
andre@0: 	 */
andre@0: 	PR_ASSERT(rwlock->rw_lock_cnt != 0);
andre@0: 	if (rwlock->rw_lock_cnt > 0) {
andre@0: 
andre@0: 		/*
andre@0: 		 * decrement read-lock count
andre@0: 		 */
andre@0: 		rwlock->rw_lock_cnt--;
andre@0: 		if (rwlock->rw_lock_cnt == 0) {
andre@0: 			/*
andre@0: 			 * lock is not read-locked anymore; wakeup a waiting writer
andre@0: 			 */
andre@0: 			if (rwlock->rw_writer_cnt > 0)
andre@0: 				PR_NotifyCondVar(rwlock->rw_writer_waitq);
andre@0: 		}
andre@0: 	} else {
andre@0: 		PR_ASSERT(rwlock->rw_lock_cnt == -1);
andre@0: 
andre@0: 		rwlock->rw_lock_cnt = 0;
andre@0: #ifdef DEBUG
andre@0:     	PR_ASSERT(rwlock->rw_owner == me);
andre@0:     	rwlock->rw_owner = NULL;
andre@0: #endif
andre@0: 		/*
andre@0: 		 * wakeup a writer, if present; preference for writers
andre@0: 		 */
andre@0: 		if (rwlock->rw_writer_cnt > 0)
andre@0: 			PR_NotifyCondVar(rwlock->rw_writer_waitq);
andre@0: 		/*
andre@0: 		 * else, wakeup all readers, if any
andre@0: 		 */
andre@0: 		else if (rwlock->rw_reader_cnt > 0)
andre@0: 			PR_NotifyAllCondVar(rwlock->rw_reader_waitq);
andre@0: 	}
andre@0: 	PR_Unlock(rwlock->rw_lock);
andre@0: #endif
andre@0: 
andre@0: #ifdef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 	/*
andre@0: 	 * update thread's lock rank
andre@0: 	 */
andre@0: 	if (rwlock->rw_rank != PR_RWLOCK_RANK_NONE)
andre@0: 		_PR_UNSET_THREAD_RWLOCK_RANK(rwlock);
andre@0: #endif
andre@0: 	return;
andre@0: }
andre@0: 
andre@0: #ifndef _PR_RWLOCK_RANK_ORDER_DEBUG
andre@0: 
andre@0: void _PR_InitRWLocks(void) { }
andre@0: 
andre@0: #else
andre@0: 
andre@0: void _PR_InitRWLocks(void)
andre@0: {
andre@0: 	/*
andre@0: 	 * allocated thread-private-data index for rwlock list
andre@0: 	 */
andre@0: 	if (PR_NewThreadPrivateIndex(&pr_thread_rwlock_key,
andre@0: 			_PR_RELEASE_LOCK_STACK) == PR_FAILURE) {
andre@0: 		pr_thread_rwlock_alloc_failed = 1;
andre@0: 		return;
andre@0: 	}
andre@0: }
andre@0: 
andre@0: /*
andre@0:  * _PR_SET_THREAD_RWLOCK_RANK
andre@0:  *		Set a thread's lock rank, which is the highest of the ranks of all
andre@0:  *		the locks held by the thread. Pointers to the locks are added to a
andre@0:  *		per-thread list, which is anchored off a thread-private data key.
andre@0:  */
andre@0: 
andre@0: static void
andre@0: _PR_SET_THREAD_RWLOCK_RANK(PRRWLock *rwlock)
andre@0: {
andre@0: thread_rwlock_stack *lock_stack;
andre@0: PRStatus rv;
andre@0: 
andre@0: 	/*
andre@0: 	 * allocate a lock stack
andre@0: 	 */
andre@0: 	if ((lock_stack = PR_GetThreadPrivate(pr_thread_rwlock_key)) == NULL) {
andre@0: 		lock_stack = (thread_rwlock_stack *)
andre@0: 						PR_CALLOC(1 * sizeof(thread_rwlock_stack));
andre@0: 		if (lock_stack) {
andre@0: 			rv = PR_SetThreadPrivate(pr_thread_rwlock_key, lock_stack);
andre@0: 			if (rv == PR_FAILURE) {
andre@0: 				PR_DELETE(lock_stack);
andre@0: 				pr_thread_rwlock_alloc_failed = 1;
andre@0: 				return;
andre@0: 			}
andre@0: 		} else {
andre@0: 			pr_thread_rwlock_alloc_failed = 1;
andre@0: 			return;
andre@0: 		}
andre@0: 	}
andre@0: 	/*
andre@0: 	 * add rwlock to lock stack, if limit is not exceeded
andre@0: 	 */
andre@0: 	if (lock_stack) {
andre@0: 		if (lock_stack->trs_index < _PR_RWLOCK_RANK_ORDER_LIMIT)
andre@0: 			lock_stack->trs_stack[lock_stack->trs_index++] = rwlock;	
andre@0: 	}
andre@0: }
andre@0: 
andre@0: static void
andre@0: _PR_RELEASE_LOCK_STACK(void *lock_stack)
andre@0: {
andre@0: 	PR_ASSERT(lock_stack);
andre@0: 	PR_DELETE(lock_stack);
andre@0: }
andre@0: 
andre@0: /*
andre@0:  * _PR_GET_THREAD_RWLOCK_RANK
andre@0:  *
andre@0:  *		return thread's lock rank. If thread-private-data for the lock
andre@0:  *		stack is not allocated, return PR_RWLOCK_RANK_NONE.
andre@0:  */
andre@0: 	
andre@0: static PRUint32
andre@0: _PR_GET_THREAD_RWLOCK_RANK(void)
andre@0: {
andre@0: 	thread_rwlock_stack *lock_stack;
andre@0: 
andre@0: 	lock_stack = PR_GetThreadPrivate(pr_thread_rwlock_key);
andre@0: 	if (lock_stack == NULL || lock_stack->trs_index == 0)
andre@0: 		return (PR_RWLOCK_RANK_NONE);
andre@0: 	else
andre@0: 		return(lock_stack->trs_stack[lock_stack->trs_index - 1]->rw_rank);
andre@0: }
andre@0: 
andre@0: /*
andre@0:  * _PR_UNSET_THREAD_RWLOCK_RANK
andre@0:  *
andre@0:  *		remove the rwlock from the lock stack. Since locks may not be
andre@0:  *		unlocked in a FIFO order, the entire lock stack is searched.
andre@0:  */
andre@0: 	
andre@0: static void
andre@0: _PR_UNSET_THREAD_RWLOCK_RANK(PRRWLock *rwlock)
andre@0: {
andre@0: 	thread_rwlock_stack *lock_stack;
andre@0: 	int new_index = 0, index, done = 0;
andre@0: 
andre@0: 	lock_stack = PR_GetThreadPrivate(pr_thread_rwlock_key);
andre@0: 
andre@0: 	PR_ASSERT(lock_stack != NULL);
andre@0: 
andre@0: 	for (index = lock_stack->trs_index - 1; index >= 0; index--) {
andre@0: 		if (!done && (lock_stack->trs_stack[index] == rwlock))  {
andre@0: 			/*
andre@0: 			 * reset the slot for rwlock
andre@0: 			 */
andre@0: 			lock_stack->trs_stack[index] = NULL;
andre@0: 			done = 1;
andre@0: 		}
andre@0: 		/*
andre@0: 		 * search for the lowest-numbered empty slot, above which there are
andre@0: 		 * no non-empty slots
andre@0: 		 */
andre@0: 		if (!new_index && (lock_stack->trs_stack[index] != NULL))
andre@0: 			new_index = index + 1;
andre@0: 		if (done && new_index)
andre@0: 			break;
andre@0: 	}
andre@0: 	/*
andre@0: 	 * set top of stack to highest numbered empty slot
andre@0: 	 */
andre@0: 	lock_stack->trs_index = new_index;
andre@0: 
andre@0: }
andre@0: 
andre@0: #endif 	/* _PR_RWLOCK_RANK_ORDER_DEBUG */