| /* 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 "nssrwlk.h" |
| #include "nspr.h" |
| |
| PR_BEGIN_EXTERN_C |
| |
| /* |
| * Reader-writer lock |
| */ |
| struct nssRWLockStr { |
| PZLock *rw_lock; |
| char *rw_name; /* lock name */ |
| PRUint32 rw_rank; /* rank of the lock */ |
| PRInt32 rw_writer_locks; /* == 0, if unlocked */ |
| PRInt32 rw_reader_locks; /* == 0, if unlocked */ |
| /* > 0 , # of read locks */ |
| PRUint32 rw_waiting_readers; /* number of waiting readers */ |
| PRUint32 rw_waiting_writers; /* number of waiting writers */ |
| PZCondVar *rw_reader_waitq; /* cvar for readers */ |
| PZCondVar *rw_writer_waitq; /* cvar for writers */ |
| PRThread *rw_owner; /* lock owner for write-lock */ |
| /* Non-null if write lock held. */ |
| }; |
| |
| PR_END_EXTERN_C |
| |
| #include <string.h> |
| |
| #ifdef DEBUG_RANK_ORDER |
| #define NSS_RWLOCK_RANK_ORDER_DEBUG /* enable deadlock detection using \ |
| rank-order for locks \ |
| */ |
| #endif |
| |
| #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
| |
| static PRUintn nss_thread_rwlock_initialized; |
| static PRUintn nss_thread_rwlock; /* TPD key for lock stack */ |
| static PRUintn nss_thread_rwlock_alloc_failed; |
| |
| #define _NSS_RWLOCK_RANK_ORDER_LIMIT 10 |
| |
| typedef struct thread_rwlock_stack { |
| PRInt32 trs_index; /* top of stack */ |
| NSSRWLock *trs_stack[_NSS_RWLOCK_RANK_ORDER_LIMIT]; /* stack of lock |
| pointers */ |
| } thread_rwlock_stack; |
| |
| /* forward static declarations. */ |
| static PRUint32 nssRWLock_GetThreadRank(PRThread *me); |
| static void nssRWLock_SetThreadRank(PRThread *me, NSSRWLock *rwlock); |
| static void nssRWLock_UnsetThreadRank(PRThread *me, NSSRWLock *rwlock); |
| static void nssRWLock_ReleaseLockStack(void *lock_stack); |
| |
| #endif |
| |
| #define UNTIL(x) while (!(x)) |
| |
| /* |
| * Reader/Writer Locks |
| */ |
| |
| /* |
| * NSSRWLock_New |
| * Create a reader-writer lock, with the given lock rank and lock name |
| * |
| */ |
| |
| NSSRWLock * |
| NSSRWLock_New(PRUint32 lock_rank, const char *lock_name) |
| { |
| NSSRWLock *rwlock; |
| |
| rwlock = PR_NEWZAP(NSSRWLock); |
| if (rwlock == NULL) |
| return NULL; |
| |
| rwlock->rw_lock = PZ_NewLock(nssILockRWLock); |
| if (rwlock->rw_lock == NULL) { |
| goto loser; |
| } |
| rwlock->rw_reader_waitq = PZ_NewCondVar(rwlock->rw_lock); |
| if (rwlock->rw_reader_waitq == NULL) { |
| goto loser; |
| } |
| rwlock->rw_writer_waitq = PZ_NewCondVar(rwlock->rw_lock); |
| if (rwlock->rw_writer_waitq == NULL) { |
| goto loser; |
| } |
| if (lock_name != NULL) { |
| rwlock->rw_name = (char *)PR_Malloc((PRUint32)strlen(lock_name) + 1); |
| if (rwlock->rw_name == NULL) { |
| goto loser; |
| } |
| strcpy(rwlock->rw_name, lock_name); |
| } else { |
| rwlock->rw_name = NULL; |
| } |
| rwlock->rw_rank = lock_rank; |
| rwlock->rw_waiting_readers = 0; |
| rwlock->rw_waiting_writers = 0; |
| rwlock->rw_reader_locks = 0; |
| rwlock->rw_writer_locks = 0; |
| |
| return rwlock; |
| |
| loser: |
| NSSRWLock_Destroy(rwlock); |
| return (NULL); |
| } |
| |
| /* |
| ** Destroy the given RWLock "lock". |
| */ |
| void |
| NSSRWLock_Destroy(NSSRWLock *rwlock) |
| { |
| PR_ASSERT(rwlock != NULL); |
| PR_ASSERT(rwlock->rw_waiting_readers == 0); |
| PR_ASSERT(rwlock->rw_writer_locks == 0); |
| PR_ASSERT(rwlock->rw_reader_locks == 0); |
| |
| /* XXX Shouldn't we lock the PZLock before destroying this?? */ |
| |
| if (rwlock->rw_name) |
| PR_Free(rwlock->rw_name); |
| if (rwlock->rw_reader_waitq) |
| PZ_DestroyCondVar(rwlock->rw_reader_waitq); |
| if (rwlock->rw_writer_waitq) |
| PZ_DestroyCondVar(rwlock->rw_writer_waitq); |
| if (rwlock->rw_lock) |
| PZ_DestroyLock(rwlock->rw_lock); |
| PR_DELETE(rwlock); |
| } |
| |
| /* |
| ** Read-lock the RWLock. |
| */ |
| void |
| NSSRWLock_LockRead(NSSRWLock *rwlock) |
| { |
| PRThread *me = PR_GetCurrentThread(); |
| |
| PZ_Lock(rwlock->rw_lock); |
| #ifdef NSS_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 == NSS_RWLOCK_RANK_NONE) || |
| (rwlock->rw_rank >= nssRWLock_GetThreadRank(me))); |
| #endif |
| /* |
| * wait if write-locked or if a writer is waiting; preference for writers |
| */ |
| UNTIL((rwlock->rw_owner == me) || /* I own it, or */ |
| ((rwlock->rw_owner == NULL) && /* no-one owns it, and */ |
| (rwlock->rw_waiting_writers == 0))) |
| { /* no-one is waiting to own */ |
| |
| rwlock->rw_waiting_readers++; |
| PZ_WaitCondVar(rwlock->rw_reader_waitq, PR_INTERVAL_NO_TIMEOUT); |
| rwlock->rw_waiting_readers--; |
| } |
| rwlock->rw_reader_locks++; /* Increment read-lock count */ |
| |
| PZ_Unlock(rwlock->rw_lock); |
| |
| #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
| nssRWLock_SetThreadRank(me, rwlock); /* update thread's lock rank */ |
| #endif |
| } |
| |
| /* Unlock a Read lock held on this RW lock. |
| */ |
| void |
| NSSRWLock_UnlockRead(NSSRWLock *rwlock) |
| { |
| PZ_Lock(rwlock->rw_lock); |
| |
| PR_ASSERT(rwlock->rw_reader_locks > 0); /* lock must be read locked */ |
| |
| if ((rwlock->rw_reader_locks > 0) && /* caller isn't screwey */ |
| (--rwlock->rw_reader_locks == 0) && /* not read locked any more */ |
| (rwlock->rw_owner == NULL) && /* not write locked */ |
| (rwlock->rw_waiting_writers > 0)) { /* someone's waiting. */ |
| |
| PZ_NotifyCondVar(rwlock->rw_writer_waitq); /* wake him up. */ |
| } |
| |
| PZ_Unlock(rwlock->rw_lock); |
| |
| #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
| /* |
| * update thread's lock rank |
| */ |
| nssRWLock_UnsetThreadRank(me, rwlock); |
| #endif |
| return; |
| } |
| |
| /* |
| ** Write-lock the RWLock. |
| */ |
| void |
| NSSRWLock_LockWrite(NSSRWLock *rwlock) |
| { |
| PRThread *me = PR_GetCurrentThread(); |
| |
| PZ_Lock(rwlock->rw_lock); |
| #ifdef NSS_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 == NSS_RWLOCK_RANK_NONE) || |
| (rwlock->rw_rank >= nssRWLock_GetThreadRank(me))); |
| #endif |
| /* |
| * wait if read locked or write locked. |
| */ |
| PR_ASSERT(rwlock->rw_reader_locks >= 0); |
| PR_ASSERT(me != NULL); |
| |
| UNTIL((rwlock->rw_owner == me) || /* I own write lock, or */ |
| ((rwlock->rw_owner == NULL) && /* no writer and */ |
| (rwlock->rw_reader_locks == 0))) |
| { /* no readers, either. */ |
| |
| rwlock->rw_waiting_writers++; |
| PZ_WaitCondVar(rwlock->rw_writer_waitq, PR_INTERVAL_NO_TIMEOUT); |
| rwlock->rw_waiting_writers--; |
| PR_ASSERT(rwlock->rw_reader_locks >= 0); |
| } |
| |
| PR_ASSERT(rwlock->rw_reader_locks == 0); |
| /* |
| * apply write lock |
| */ |
| rwlock->rw_owner = me; |
| rwlock->rw_writer_locks++; /* Increment write-lock count */ |
| |
| PZ_Unlock(rwlock->rw_lock); |
| |
| #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
| /* |
| * update thread's lock rank |
| */ |
| nssRWLock_SetThreadRank(me, rwlock); |
| #endif |
| } |
| |
| /* Unlock a Read lock held on this RW lock. |
| */ |
| void |
| NSSRWLock_UnlockWrite(NSSRWLock *rwlock) |
| { |
| PRThread *me = PR_GetCurrentThread(); |
| |
| PZ_Lock(rwlock->rw_lock); |
| PR_ASSERT(rwlock->rw_owner == me); /* lock must be write-locked by me. */ |
| PR_ASSERT(rwlock->rw_writer_locks > 0); /* lock must be write locked */ |
| |
| if (rwlock->rw_owner == me && /* I own it, and */ |
| rwlock->rw_writer_locks > 0 && /* I own it, and */ |
| --rwlock->rw_writer_locks == 0) { /* I'm all done with it */ |
| |
| rwlock->rw_owner = NULL; /* I don't own it any more. */ |
| |
| /* Give preference to waiting writers. */ |
| if (rwlock->rw_waiting_writers > 0) { |
| if (rwlock->rw_reader_locks == 0) |
| PZ_NotifyCondVar(rwlock->rw_writer_waitq); |
| } else if (rwlock->rw_waiting_readers > 0) { |
| PZ_NotifyAllCondVar(rwlock->rw_reader_waitq); |
| } |
| } |
| PZ_Unlock(rwlock->rw_lock); |
| |
| #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
| /* |
| * update thread's lock rank |
| */ |
| nssRWLock_UnsetThreadRank(me, rwlock); |
| #endif |
| return; |
| } |
| |
| /* This is primarily for debugging, i.e. for inclusion in ASSERT calls. */ |
| PRBool |
| NSSRWLock_HaveWriteLock(NSSRWLock *rwlock) |
| { |
| PRBool ownWriteLock; |
| PRThread *me = PR_GetCurrentThread(); |
| |
| /* This lock call isn't really necessary. |
| ** If this thread is the owner, that fact cannot change during this call, |
| ** because this thread is in this call. |
| ** If this thread is NOT the owner, the owner could change, but it |
| ** could not become this thread. |
| */ |
| #if UNNECESSARY |
| PZ_Lock(rwlock->rw_lock); |
| #endif |
| ownWriteLock = (PRBool)(me == rwlock->rw_owner); |
| #if UNNECESSARY |
| PZ_Unlock(rwlock->rw_lock); |
| #endif |
| return ownWriteLock; |
| } |
| |
| #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
| |
| /* |
| * nssRWLock_SetThreadRank |
| * 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 |
| nssRWLock_SetThreadRank(PRThread *me, NSSRWLock *rwlock) |
| { |
| thread_rwlock_stack *lock_stack; |
| PRStatus rv; |
| |
| /* |
| * allocated thread-private-data for rwlock list, if not already allocated |
| */ |
| if (!nss_thread_rwlock_initialized) { |
| /* |
| * allocate tpd, only if not failed already |
| */ |
| if (!nss_thread_rwlock_alloc_failed) { |
| if (PR_NewThreadPrivateIndex(&nss_thread_rwlock, |
| nssRWLock_ReleaseLockStack) == PR_FAILURE) { |
| nss_thread_rwlock_alloc_failed = 1; |
| return; |
| } |
| } else |
| return; |
| } |
| /* |
| * allocate a lock stack |
| */ |
| if ((lock_stack = PR_GetThreadPrivate(nss_thread_rwlock)) == NULL) { |
| lock_stack = (thread_rwlock_stack *) |
| PR_CALLOC(1 * sizeof(thread_rwlock_stack)); |
| if (lock_stack) { |
| rv = PR_SetThreadPrivate(nss_thread_rwlock, lock_stack); |
| if (rv == PR_FAILURE) { |
| PR_DELETE(lock_stack); |
| nss_thread_rwlock_alloc_failed = 1; |
| return; |
| } |
| } else { |
| nss_thread_rwlock_alloc_failed = 1; |
| return; |
| } |
| } |
| /* |
| * add rwlock to lock stack, if limit is not exceeded |
| */ |
| if (lock_stack) { |
| if (lock_stack->trs_index < _NSS_RWLOCK_RANK_ORDER_LIMIT) |
| lock_stack->trs_stack[lock_stack->trs_index++] = rwlock; |
| } |
| nss_thread_rwlock_initialized = 1; |
| } |
| |
| static void |
| nssRWLock_ReleaseLockStack(void *lock_stack) |
| { |
| PR_ASSERT(lock_stack); |
| PR_DELETE(lock_stack); |
| } |
| |
| /* |
| * nssRWLock_GetThreadRank |
| * |
| * return thread's lock rank. If thread-private-data for the lock |
| * stack is not allocated, return NSS_RWLOCK_RANK_NONE. |
| */ |
| |
| static PRUint32 |
| nssRWLock_GetThreadRank(PRThread *me) |
| { |
| thread_rwlock_stack *lock_stack; |
| |
| if (nss_thread_rwlock_initialized) { |
| if ((lock_stack = PR_GetThreadPrivate(nss_thread_rwlock)) == NULL) |
| return (NSS_RWLOCK_RANK_NONE); |
| else |
| return (lock_stack->trs_stack[lock_stack->trs_index - 1]->rw_rank); |
| |
| } else |
| return (NSS_RWLOCK_RANK_NONE); |
| } |
| |
| /* |
| * nssRWLock_UnsetThreadRank |
| * |
| * 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 |
| nssRWLock_UnsetThreadRank(PRThread *me, NSSRWLock *rwlock) |
| { |
| thread_rwlock_stack *lock_stack; |
| int new_index = 0, index, done = 0; |
| |
| if (!nss_thread_rwlock_initialized) |
| return; |
| |
| lock_stack = PR_GetThreadPrivate(nss_thread_rwlock); |
| |
| PR_ASSERT(lock_stack != NULL); |
| |
| index = lock_stack->trs_index - 1; |
| while (index-- >= 0) { |
| if ((lock_stack->trs_stack[index] == rwlock) && !done) { |
| /* |
| * 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 ((lock_stack->trs_stack[index] != NULL) && !new_index) |
| 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 /* NSS_RWLOCK_RANK_ORDER_DEBUG */ |