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