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