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