security/nss/lib/util/nssrwlk.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 #include "nssrwlk.h"
michael@0 6 #include "nspr.h"
michael@0 7
michael@0 8 PR_BEGIN_EXTERN_C
michael@0 9
michael@0 10 /*
michael@0 11 * Reader-writer lock
michael@0 12 */
michael@0 13 struct nssRWLockStr {
michael@0 14 PZLock * rw_lock;
michael@0 15 char * rw_name; /* lock name */
michael@0 16 PRUint32 rw_rank; /* rank of the lock */
michael@0 17 PRInt32 rw_writer_locks; /* == 0, if unlocked */
michael@0 18 PRInt32 rw_reader_locks; /* == 0, if unlocked */
michael@0 19 /* > 0 , # of read locks */
michael@0 20 PRUint32 rw_waiting_readers; /* number of waiting readers */
michael@0 21 PRUint32 rw_waiting_writers; /* number of waiting writers */
michael@0 22 PZCondVar * rw_reader_waitq; /* cvar for readers */
michael@0 23 PZCondVar * rw_writer_waitq; /* cvar for writers */
michael@0 24 PRThread * rw_owner; /* lock owner for write-lock */
michael@0 25 /* Non-null if write lock held. */
michael@0 26 };
michael@0 27
michael@0 28 PR_END_EXTERN_C
michael@0 29
michael@0 30 #include <string.h>
michael@0 31
michael@0 32 #ifdef DEBUG_RANK_ORDER
michael@0 33 #define NSS_RWLOCK_RANK_ORDER_DEBUG /* enable deadlock detection using
michael@0 34 rank-order for locks
michael@0 35 */
michael@0 36 #endif
michael@0 37
michael@0 38 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 39
michael@0 40 static PRUintn nss_thread_rwlock_initialized;
michael@0 41 static PRUintn nss_thread_rwlock; /* TPD key for lock stack */
michael@0 42 static PRUintn nss_thread_rwlock_alloc_failed;
michael@0 43
michael@0 44 #define _NSS_RWLOCK_RANK_ORDER_LIMIT 10
michael@0 45
michael@0 46 typedef struct thread_rwlock_stack {
michael@0 47 PRInt32 trs_index; /* top of stack */
michael@0 48 NSSRWLock *trs_stack[_NSS_RWLOCK_RANK_ORDER_LIMIT]; /* stack of lock
michael@0 49 pointers */
michael@0 50 } thread_rwlock_stack;
michael@0 51
michael@0 52 /* forward static declarations. */
michael@0 53 static PRUint32 nssRWLock_GetThreadRank(PRThread *me);
michael@0 54 static void nssRWLock_SetThreadRank(PRThread *me, NSSRWLock *rwlock);
michael@0 55 static void nssRWLock_UnsetThreadRank(PRThread *me, NSSRWLock *rwlock);
michael@0 56 static void nssRWLock_ReleaseLockStack(void *lock_stack);
michael@0 57
michael@0 58 #endif
michael@0 59
michael@0 60 #define UNTIL(x) while(!(x))
michael@0 61
michael@0 62 /*
michael@0 63 * Reader/Writer Locks
michael@0 64 */
michael@0 65
michael@0 66 /*
michael@0 67 * NSSRWLock_New
michael@0 68 * Create a reader-writer lock, with the given lock rank and lock name
michael@0 69 *
michael@0 70 */
michael@0 71
michael@0 72 NSSRWLock *
michael@0 73 NSSRWLock_New(PRUint32 lock_rank, const char *lock_name)
michael@0 74 {
michael@0 75 NSSRWLock *rwlock;
michael@0 76
michael@0 77 rwlock = PR_NEWZAP(NSSRWLock);
michael@0 78 if (rwlock == NULL)
michael@0 79 return NULL;
michael@0 80
michael@0 81 rwlock->rw_lock = PZ_NewLock(nssILockRWLock);
michael@0 82 if (rwlock->rw_lock == NULL) {
michael@0 83 goto loser;
michael@0 84 }
michael@0 85 rwlock->rw_reader_waitq = PZ_NewCondVar(rwlock->rw_lock);
michael@0 86 if (rwlock->rw_reader_waitq == NULL) {
michael@0 87 goto loser;
michael@0 88 }
michael@0 89 rwlock->rw_writer_waitq = PZ_NewCondVar(rwlock->rw_lock);
michael@0 90 if (rwlock->rw_writer_waitq == NULL) {
michael@0 91 goto loser;
michael@0 92 }
michael@0 93 if (lock_name != NULL) {
michael@0 94 rwlock->rw_name = (char*) PR_Malloc(strlen(lock_name) + 1);
michael@0 95 if (rwlock->rw_name == NULL) {
michael@0 96 goto loser;
michael@0 97 }
michael@0 98 strcpy(rwlock->rw_name, lock_name);
michael@0 99 } else {
michael@0 100 rwlock->rw_name = NULL;
michael@0 101 }
michael@0 102 rwlock->rw_rank = lock_rank;
michael@0 103 rwlock->rw_waiting_readers = 0;
michael@0 104 rwlock->rw_waiting_writers = 0;
michael@0 105 rwlock->rw_reader_locks = 0;
michael@0 106 rwlock->rw_writer_locks = 0;
michael@0 107
michael@0 108 return rwlock;
michael@0 109
michael@0 110 loser:
michael@0 111 NSSRWLock_Destroy(rwlock);
michael@0 112 return(NULL);
michael@0 113 }
michael@0 114
michael@0 115 /*
michael@0 116 ** Destroy the given RWLock "lock".
michael@0 117 */
michael@0 118 void
michael@0 119 NSSRWLock_Destroy(NSSRWLock *rwlock)
michael@0 120 {
michael@0 121 PR_ASSERT(rwlock != NULL);
michael@0 122 PR_ASSERT(rwlock->rw_waiting_readers == 0);
michael@0 123
michael@0 124 /* XXX Shouldn't we lock the PZLock before destroying this?? */
michael@0 125
michael@0 126 if (rwlock->rw_name)
michael@0 127 PR_Free(rwlock->rw_name);
michael@0 128 if (rwlock->rw_reader_waitq)
michael@0 129 PZ_DestroyCondVar(rwlock->rw_reader_waitq);
michael@0 130 if (rwlock->rw_writer_waitq)
michael@0 131 PZ_DestroyCondVar(rwlock->rw_writer_waitq);
michael@0 132 if (rwlock->rw_lock)
michael@0 133 PZ_DestroyLock(rwlock->rw_lock);
michael@0 134 PR_DELETE(rwlock);
michael@0 135 }
michael@0 136
michael@0 137 /*
michael@0 138 ** Read-lock the RWLock.
michael@0 139 */
michael@0 140 void
michael@0 141 NSSRWLock_LockRead(NSSRWLock *rwlock)
michael@0 142 {
michael@0 143 PRThread *me = PR_GetCurrentThread();
michael@0 144
michael@0 145 PZ_Lock(rwlock->rw_lock);
michael@0 146 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 147
michael@0 148 /*
michael@0 149 * assert that rank ordering is not violated; the rank of 'rwlock' should
michael@0 150 * be equal to or greater than the highest rank of all the locks held by
michael@0 151 * the thread.
michael@0 152 */
michael@0 153 PR_ASSERT((rwlock->rw_rank == NSS_RWLOCK_RANK_NONE) ||
michael@0 154 (rwlock->rw_rank >= nssRWLock_GetThreadRank(me)));
michael@0 155 #endif
michael@0 156 /*
michael@0 157 * wait if write-locked or if a writer is waiting; preference for writers
michael@0 158 */
michael@0 159 UNTIL ( (rwlock->rw_owner == me) || /* I own it, or */
michael@0 160 ((rwlock->rw_owner == NULL) && /* no-one owns it, and */
michael@0 161 (rwlock->rw_waiting_writers == 0))) { /* no-one is waiting to own */
michael@0 162
michael@0 163 rwlock->rw_waiting_readers++;
michael@0 164 PZ_WaitCondVar(rwlock->rw_reader_waitq, PR_INTERVAL_NO_TIMEOUT);
michael@0 165 rwlock->rw_waiting_readers--;
michael@0 166 }
michael@0 167 rwlock->rw_reader_locks++; /* Increment read-lock count */
michael@0 168
michael@0 169 PZ_Unlock(rwlock->rw_lock);
michael@0 170
michael@0 171 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 172 nssRWLock_SetThreadRank(me, rwlock);/* update thread's lock rank */
michael@0 173 #endif
michael@0 174 }
michael@0 175
michael@0 176 /* Unlock a Read lock held on this RW lock.
michael@0 177 */
michael@0 178 void
michael@0 179 NSSRWLock_UnlockRead(NSSRWLock *rwlock)
michael@0 180 {
michael@0 181 PZ_Lock(rwlock->rw_lock);
michael@0 182
michael@0 183 PR_ASSERT(rwlock->rw_reader_locks > 0); /* lock must be read locked */
michael@0 184
michael@0 185 if (( rwlock->rw_reader_locks > 0) && /* caller isn't screwey */
michael@0 186 (--rwlock->rw_reader_locks == 0) && /* not read locked any more */
michael@0 187 ( rwlock->rw_owner == NULL) && /* not write locked */
michael@0 188 ( rwlock->rw_waiting_writers > 0)) { /* someone's waiting. */
michael@0 189
michael@0 190 PZ_NotifyCondVar(rwlock->rw_writer_waitq); /* wake him up. */
michael@0 191 }
michael@0 192
michael@0 193 PZ_Unlock(rwlock->rw_lock);
michael@0 194
michael@0 195 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 196 /*
michael@0 197 * update thread's lock rank
michael@0 198 */
michael@0 199 nssRWLock_UnsetThreadRank(me, rwlock);
michael@0 200 #endif
michael@0 201 return;
michael@0 202 }
michael@0 203
michael@0 204 /*
michael@0 205 ** Write-lock the RWLock.
michael@0 206 */
michael@0 207 void
michael@0 208 NSSRWLock_LockWrite(NSSRWLock *rwlock)
michael@0 209 {
michael@0 210 PRThread *me = PR_GetCurrentThread();
michael@0 211
michael@0 212 PZ_Lock(rwlock->rw_lock);
michael@0 213 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 214 /*
michael@0 215 * assert that rank ordering is not violated; the rank of 'rwlock' should
michael@0 216 * be equal to or greater than the highest rank of all the locks held by
michael@0 217 * the thread.
michael@0 218 */
michael@0 219 PR_ASSERT((rwlock->rw_rank == NSS_RWLOCK_RANK_NONE) ||
michael@0 220 (rwlock->rw_rank >= nssRWLock_GetThreadRank(me)));
michael@0 221 #endif
michael@0 222 /*
michael@0 223 * wait if read locked or write locked.
michael@0 224 */
michael@0 225 PR_ASSERT(rwlock->rw_reader_locks >= 0);
michael@0 226 PR_ASSERT(me != NULL);
michael@0 227
michael@0 228 UNTIL ( (rwlock->rw_owner == me) || /* I own write lock, or */
michael@0 229 ((rwlock->rw_owner == NULL) && /* no writer and */
michael@0 230 (rwlock->rw_reader_locks == 0))) { /* no readers, either. */
michael@0 231
michael@0 232 rwlock->rw_waiting_writers++;
michael@0 233 PZ_WaitCondVar(rwlock->rw_writer_waitq, PR_INTERVAL_NO_TIMEOUT);
michael@0 234 rwlock->rw_waiting_writers--;
michael@0 235 PR_ASSERT(rwlock->rw_reader_locks >= 0);
michael@0 236 }
michael@0 237
michael@0 238 PR_ASSERT(rwlock->rw_reader_locks == 0);
michael@0 239 /*
michael@0 240 * apply write lock
michael@0 241 */
michael@0 242 rwlock->rw_owner = me;
michael@0 243 rwlock->rw_writer_locks++; /* Increment write-lock count */
michael@0 244
michael@0 245 PZ_Unlock(rwlock->rw_lock);
michael@0 246
michael@0 247 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 248 /*
michael@0 249 * update thread's lock rank
michael@0 250 */
michael@0 251 nssRWLock_SetThreadRank(me,rwlock);
michael@0 252 #endif
michael@0 253 }
michael@0 254
michael@0 255 /* Unlock a Read lock held on this RW lock.
michael@0 256 */
michael@0 257 void
michael@0 258 NSSRWLock_UnlockWrite(NSSRWLock *rwlock)
michael@0 259 {
michael@0 260 PRThread *me = PR_GetCurrentThread();
michael@0 261
michael@0 262 PZ_Lock(rwlock->rw_lock);
michael@0 263 PR_ASSERT(rwlock->rw_owner == me); /* lock must be write-locked by me. */
michael@0 264 PR_ASSERT(rwlock->rw_writer_locks > 0); /* lock must be write locked */
michael@0 265
michael@0 266 if ( rwlock->rw_owner == me && /* I own it, and */
michael@0 267 rwlock->rw_writer_locks > 0 && /* I own it, and */
michael@0 268 --rwlock->rw_writer_locks == 0) { /* I'm all done with it */
michael@0 269
michael@0 270 rwlock->rw_owner = NULL; /* I don't own it any more. */
michael@0 271
michael@0 272 /* Give preference to waiting writers. */
michael@0 273 if (rwlock->rw_waiting_writers > 0) {
michael@0 274 if (rwlock->rw_reader_locks == 0)
michael@0 275 PZ_NotifyCondVar(rwlock->rw_writer_waitq);
michael@0 276 } else if (rwlock->rw_waiting_readers > 0) {
michael@0 277 PZ_NotifyAllCondVar(rwlock->rw_reader_waitq);
michael@0 278 }
michael@0 279 }
michael@0 280 PZ_Unlock(rwlock->rw_lock);
michael@0 281
michael@0 282 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 283 /*
michael@0 284 * update thread's lock rank
michael@0 285 */
michael@0 286 nssRWLock_UnsetThreadRank(me, rwlock);
michael@0 287 #endif
michael@0 288 return;
michael@0 289 }
michael@0 290
michael@0 291 /* This is primarily for debugging, i.e. for inclusion in ASSERT calls. */
michael@0 292 PRBool
michael@0 293 NSSRWLock_HaveWriteLock(NSSRWLock *rwlock) {
michael@0 294 PRBool ownWriteLock;
michael@0 295 PRThread *me = PR_GetCurrentThread();
michael@0 296
michael@0 297 /* This lock call isn't really necessary.
michael@0 298 ** If this thread is the owner, that fact cannot change during this call,
michael@0 299 ** because this thread is in this call.
michael@0 300 ** If this thread is NOT the owner, the owner could change, but it
michael@0 301 ** could not become this thread.
michael@0 302 */
michael@0 303 #if UNNECESSARY
michael@0 304 PZ_Lock(rwlock->rw_lock);
michael@0 305 #endif
michael@0 306 ownWriteLock = (PRBool)(me == rwlock->rw_owner);
michael@0 307 #if UNNECESSARY
michael@0 308 PZ_Unlock(rwlock->rw_lock);
michael@0 309 #endif
michael@0 310 return ownWriteLock;
michael@0 311 }
michael@0 312
michael@0 313 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG
michael@0 314
michael@0 315 /*
michael@0 316 * nssRWLock_SetThreadRank
michael@0 317 * Set a thread's lock rank, which is the highest of the ranks of all
michael@0 318 * the locks held by the thread. Pointers to the locks are added to a
michael@0 319 * per-thread list, which is anchored off a thread-private data key.
michael@0 320 */
michael@0 321
michael@0 322 static void
michael@0 323 nssRWLock_SetThreadRank(PRThread *me, NSSRWLock *rwlock)
michael@0 324 {
michael@0 325 thread_rwlock_stack *lock_stack;
michael@0 326 PRStatus rv;
michael@0 327
michael@0 328 /*
michael@0 329 * allocated thread-private-data for rwlock list, if not already allocated
michael@0 330 */
michael@0 331 if (!nss_thread_rwlock_initialized) {
michael@0 332 /*
michael@0 333 * allocate tpd, only if not failed already
michael@0 334 */
michael@0 335 if (!nss_thread_rwlock_alloc_failed) {
michael@0 336 if (PR_NewThreadPrivateIndex(&nss_thread_rwlock,
michael@0 337 nssRWLock_ReleaseLockStack)
michael@0 338 == PR_FAILURE) {
michael@0 339 nss_thread_rwlock_alloc_failed = 1;
michael@0 340 return;
michael@0 341 }
michael@0 342 } else
michael@0 343 return;
michael@0 344 }
michael@0 345 /*
michael@0 346 * allocate a lock stack
michael@0 347 */
michael@0 348 if ((lock_stack = PR_GetThreadPrivate(nss_thread_rwlock)) == NULL) {
michael@0 349 lock_stack = (thread_rwlock_stack *)
michael@0 350 PR_CALLOC(1 * sizeof(thread_rwlock_stack));
michael@0 351 if (lock_stack) {
michael@0 352 rv = PR_SetThreadPrivate(nss_thread_rwlock, lock_stack);
michael@0 353 if (rv == PR_FAILURE) {
michael@0 354 PR_DELETE(lock_stack);
michael@0 355 nss_thread_rwlock_alloc_failed = 1;
michael@0 356 return;
michael@0 357 }
michael@0 358 } else {
michael@0 359 nss_thread_rwlock_alloc_failed = 1;
michael@0 360 return;
michael@0 361 }
michael@0 362 }
michael@0 363 /*
michael@0 364 * add rwlock to lock stack, if limit is not exceeded
michael@0 365 */
michael@0 366 if (lock_stack) {
michael@0 367 if (lock_stack->trs_index < _NSS_RWLOCK_RANK_ORDER_LIMIT)
michael@0 368 lock_stack->trs_stack[lock_stack->trs_index++] = rwlock;
michael@0 369 }
michael@0 370 nss_thread_rwlock_initialized = 1;
michael@0 371 }
michael@0 372
michael@0 373 static void
michael@0 374 nssRWLock_ReleaseLockStack(void *lock_stack)
michael@0 375 {
michael@0 376 PR_ASSERT(lock_stack);
michael@0 377 PR_DELETE(lock_stack);
michael@0 378 }
michael@0 379
michael@0 380 /*
michael@0 381 * nssRWLock_GetThreadRank
michael@0 382 *
michael@0 383 * return thread's lock rank. If thread-private-data for the lock
michael@0 384 * stack is not allocated, return NSS_RWLOCK_RANK_NONE.
michael@0 385 */
michael@0 386
michael@0 387 static PRUint32
michael@0 388 nssRWLock_GetThreadRank(PRThread *me)
michael@0 389 {
michael@0 390 thread_rwlock_stack *lock_stack;
michael@0 391
michael@0 392 if (nss_thread_rwlock_initialized) {
michael@0 393 if ((lock_stack = PR_GetThreadPrivate(nss_thread_rwlock)) == NULL)
michael@0 394 return (NSS_RWLOCK_RANK_NONE);
michael@0 395 else
michael@0 396 return(lock_stack->trs_stack[lock_stack->trs_index - 1]->rw_rank);
michael@0 397
michael@0 398 } else
michael@0 399 return (NSS_RWLOCK_RANK_NONE);
michael@0 400 }
michael@0 401
michael@0 402 /*
michael@0 403 * nssRWLock_UnsetThreadRank
michael@0 404 *
michael@0 405 * remove the rwlock from the lock stack. Since locks may not be
michael@0 406 * unlocked in a FIFO order, the entire lock stack is searched.
michael@0 407 */
michael@0 408
michael@0 409 static void
michael@0 410 nssRWLock_UnsetThreadRank(PRThread *me, NSSRWLock *rwlock)
michael@0 411 {
michael@0 412 thread_rwlock_stack *lock_stack;
michael@0 413 int new_index = 0, index, done = 0;
michael@0 414
michael@0 415 if (!nss_thread_rwlock_initialized)
michael@0 416 return;
michael@0 417
michael@0 418 lock_stack = PR_GetThreadPrivate(nss_thread_rwlock);
michael@0 419
michael@0 420 PR_ASSERT(lock_stack != NULL);
michael@0 421
michael@0 422 index = lock_stack->trs_index - 1;
michael@0 423 while (index-- >= 0) {
michael@0 424 if ((lock_stack->trs_stack[index] == rwlock) && !done) {
michael@0 425 /*
michael@0 426 * reset the slot for rwlock
michael@0 427 */
michael@0 428 lock_stack->trs_stack[index] = NULL;
michael@0 429 done = 1;
michael@0 430 }
michael@0 431 /*
michael@0 432 * search for the lowest-numbered empty slot, above which there are
michael@0 433 * no non-empty slots
michael@0 434 */
michael@0 435 if ((lock_stack->trs_stack[index] != NULL) && !new_index)
michael@0 436 new_index = index + 1;
michael@0 437 if (done && new_index)
michael@0 438 break;
michael@0 439 }
michael@0 440 /*
michael@0 441 * set top of stack to highest numbered empty slot
michael@0 442 */
michael@0 443 lock_stack->trs_index = new_index;
michael@0 444
michael@0 445 }
michael@0 446
michael@0 447 #endif /* NSS_RWLOCK_RANK_ORDER_DEBUG */

mercurial