|
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(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 |
|
124 /* XXX Shouldn't we lock the PZLock before destroying this?? */ |
|
125 |
|
126 if (rwlock->rw_name) |
|
127 PR_Free(rwlock->rw_name); |
|
128 if (rwlock->rw_reader_waitq) |
|
129 PZ_DestroyCondVar(rwlock->rw_reader_waitq); |
|
130 if (rwlock->rw_writer_waitq) |
|
131 PZ_DestroyCondVar(rwlock->rw_writer_waitq); |
|
132 if (rwlock->rw_lock) |
|
133 PZ_DestroyLock(rwlock->rw_lock); |
|
134 PR_DELETE(rwlock); |
|
135 } |
|
136 |
|
137 /* |
|
138 ** Read-lock the RWLock. |
|
139 */ |
|
140 void |
|
141 NSSRWLock_LockRead(NSSRWLock *rwlock) |
|
142 { |
|
143 PRThread *me = PR_GetCurrentThread(); |
|
144 |
|
145 PZ_Lock(rwlock->rw_lock); |
|
146 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
147 |
|
148 /* |
|
149 * assert that rank ordering is not violated; the rank of 'rwlock' should |
|
150 * be equal to or greater than the highest rank of all the locks held by |
|
151 * the thread. |
|
152 */ |
|
153 PR_ASSERT((rwlock->rw_rank == NSS_RWLOCK_RANK_NONE) || |
|
154 (rwlock->rw_rank >= nssRWLock_GetThreadRank(me))); |
|
155 #endif |
|
156 /* |
|
157 * wait if write-locked or if a writer is waiting; preference for writers |
|
158 */ |
|
159 UNTIL ( (rwlock->rw_owner == me) || /* I own it, or */ |
|
160 ((rwlock->rw_owner == NULL) && /* no-one owns it, and */ |
|
161 (rwlock->rw_waiting_writers == 0))) { /* no-one is waiting to own */ |
|
162 |
|
163 rwlock->rw_waiting_readers++; |
|
164 PZ_WaitCondVar(rwlock->rw_reader_waitq, PR_INTERVAL_NO_TIMEOUT); |
|
165 rwlock->rw_waiting_readers--; |
|
166 } |
|
167 rwlock->rw_reader_locks++; /* Increment read-lock count */ |
|
168 |
|
169 PZ_Unlock(rwlock->rw_lock); |
|
170 |
|
171 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
172 nssRWLock_SetThreadRank(me, rwlock);/* update thread's lock rank */ |
|
173 #endif |
|
174 } |
|
175 |
|
176 /* Unlock a Read lock held on this RW lock. |
|
177 */ |
|
178 void |
|
179 NSSRWLock_UnlockRead(NSSRWLock *rwlock) |
|
180 { |
|
181 PZ_Lock(rwlock->rw_lock); |
|
182 |
|
183 PR_ASSERT(rwlock->rw_reader_locks > 0); /* lock must be read locked */ |
|
184 |
|
185 if (( rwlock->rw_reader_locks > 0) && /* caller isn't screwey */ |
|
186 (--rwlock->rw_reader_locks == 0) && /* not read locked any more */ |
|
187 ( rwlock->rw_owner == NULL) && /* not write locked */ |
|
188 ( rwlock->rw_waiting_writers > 0)) { /* someone's waiting. */ |
|
189 |
|
190 PZ_NotifyCondVar(rwlock->rw_writer_waitq); /* wake him up. */ |
|
191 } |
|
192 |
|
193 PZ_Unlock(rwlock->rw_lock); |
|
194 |
|
195 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
196 /* |
|
197 * update thread's lock rank |
|
198 */ |
|
199 nssRWLock_UnsetThreadRank(me, rwlock); |
|
200 #endif |
|
201 return; |
|
202 } |
|
203 |
|
204 /* |
|
205 ** Write-lock the RWLock. |
|
206 */ |
|
207 void |
|
208 NSSRWLock_LockWrite(NSSRWLock *rwlock) |
|
209 { |
|
210 PRThread *me = PR_GetCurrentThread(); |
|
211 |
|
212 PZ_Lock(rwlock->rw_lock); |
|
213 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
214 /* |
|
215 * assert that rank ordering is not violated; the rank of 'rwlock' should |
|
216 * be equal to or greater than the highest rank of all the locks held by |
|
217 * the thread. |
|
218 */ |
|
219 PR_ASSERT((rwlock->rw_rank == NSS_RWLOCK_RANK_NONE) || |
|
220 (rwlock->rw_rank >= nssRWLock_GetThreadRank(me))); |
|
221 #endif |
|
222 /* |
|
223 * wait if read locked or write locked. |
|
224 */ |
|
225 PR_ASSERT(rwlock->rw_reader_locks >= 0); |
|
226 PR_ASSERT(me != NULL); |
|
227 |
|
228 UNTIL ( (rwlock->rw_owner == me) || /* I own write lock, or */ |
|
229 ((rwlock->rw_owner == NULL) && /* no writer and */ |
|
230 (rwlock->rw_reader_locks == 0))) { /* no readers, either. */ |
|
231 |
|
232 rwlock->rw_waiting_writers++; |
|
233 PZ_WaitCondVar(rwlock->rw_writer_waitq, PR_INTERVAL_NO_TIMEOUT); |
|
234 rwlock->rw_waiting_writers--; |
|
235 PR_ASSERT(rwlock->rw_reader_locks >= 0); |
|
236 } |
|
237 |
|
238 PR_ASSERT(rwlock->rw_reader_locks == 0); |
|
239 /* |
|
240 * apply write lock |
|
241 */ |
|
242 rwlock->rw_owner = me; |
|
243 rwlock->rw_writer_locks++; /* Increment write-lock count */ |
|
244 |
|
245 PZ_Unlock(rwlock->rw_lock); |
|
246 |
|
247 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
248 /* |
|
249 * update thread's lock rank |
|
250 */ |
|
251 nssRWLock_SetThreadRank(me,rwlock); |
|
252 #endif |
|
253 } |
|
254 |
|
255 /* Unlock a Read lock held on this RW lock. |
|
256 */ |
|
257 void |
|
258 NSSRWLock_UnlockWrite(NSSRWLock *rwlock) |
|
259 { |
|
260 PRThread *me = PR_GetCurrentThread(); |
|
261 |
|
262 PZ_Lock(rwlock->rw_lock); |
|
263 PR_ASSERT(rwlock->rw_owner == me); /* lock must be write-locked by me. */ |
|
264 PR_ASSERT(rwlock->rw_writer_locks > 0); /* lock must be write locked */ |
|
265 |
|
266 if ( rwlock->rw_owner == me && /* I own it, and */ |
|
267 rwlock->rw_writer_locks > 0 && /* I own it, and */ |
|
268 --rwlock->rw_writer_locks == 0) { /* I'm all done with it */ |
|
269 |
|
270 rwlock->rw_owner = NULL; /* I don't own it any more. */ |
|
271 |
|
272 /* Give preference to waiting writers. */ |
|
273 if (rwlock->rw_waiting_writers > 0) { |
|
274 if (rwlock->rw_reader_locks == 0) |
|
275 PZ_NotifyCondVar(rwlock->rw_writer_waitq); |
|
276 } else if (rwlock->rw_waiting_readers > 0) { |
|
277 PZ_NotifyAllCondVar(rwlock->rw_reader_waitq); |
|
278 } |
|
279 } |
|
280 PZ_Unlock(rwlock->rw_lock); |
|
281 |
|
282 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
283 /* |
|
284 * update thread's lock rank |
|
285 */ |
|
286 nssRWLock_UnsetThreadRank(me, rwlock); |
|
287 #endif |
|
288 return; |
|
289 } |
|
290 |
|
291 /* This is primarily for debugging, i.e. for inclusion in ASSERT calls. */ |
|
292 PRBool |
|
293 NSSRWLock_HaveWriteLock(NSSRWLock *rwlock) { |
|
294 PRBool ownWriteLock; |
|
295 PRThread *me = PR_GetCurrentThread(); |
|
296 |
|
297 /* This lock call isn't really necessary. |
|
298 ** If this thread is the owner, that fact cannot change during this call, |
|
299 ** because this thread is in this call. |
|
300 ** If this thread is NOT the owner, the owner could change, but it |
|
301 ** could not become this thread. |
|
302 */ |
|
303 #if UNNECESSARY |
|
304 PZ_Lock(rwlock->rw_lock); |
|
305 #endif |
|
306 ownWriteLock = (PRBool)(me == rwlock->rw_owner); |
|
307 #if UNNECESSARY |
|
308 PZ_Unlock(rwlock->rw_lock); |
|
309 #endif |
|
310 return ownWriteLock; |
|
311 } |
|
312 |
|
313 #ifdef NSS_RWLOCK_RANK_ORDER_DEBUG |
|
314 |
|
315 /* |
|
316 * nssRWLock_SetThreadRank |
|
317 * Set a thread's lock rank, which is the highest of the ranks of all |
|
318 * the locks held by the thread. Pointers to the locks are added to a |
|
319 * per-thread list, which is anchored off a thread-private data key. |
|
320 */ |
|
321 |
|
322 static void |
|
323 nssRWLock_SetThreadRank(PRThread *me, NSSRWLock *rwlock) |
|
324 { |
|
325 thread_rwlock_stack *lock_stack; |
|
326 PRStatus rv; |
|
327 |
|
328 /* |
|
329 * allocated thread-private-data for rwlock list, if not already allocated |
|
330 */ |
|
331 if (!nss_thread_rwlock_initialized) { |
|
332 /* |
|
333 * allocate tpd, only if not failed already |
|
334 */ |
|
335 if (!nss_thread_rwlock_alloc_failed) { |
|
336 if (PR_NewThreadPrivateIndex(&nss_thread_rwlock, |
|
337 nssRWLock_ReleaseLockStack) |
|
338 == PR_FAILURE) { |
|
339 nss_thread_rwlock_alloc_failed = 1; |
|
340 return; |
|
341 } |
|
342 } else |
|
343 return; |
|
344 } |
|
345 /* |
|
346 * allocate a lock stack |
|
347 */ |
|
348 if ((lock_stack = PR_GetThreadPrivate(nss_thread_rwlock)) == NULL) { |
|
349 lock_stack = (thread_rwlock_stack *) |
|
350 PR_CALLOC(1 * sizeof(thread_rwlock_stack)); |
|
351 if (lock_stack) { |
|
352 rv = PR_SetThreadPrivate(nss_thread_rwlock, lock_stack); |
|
353 if (rv == PR_FAILURE) { |
|
354 PR_DELETE(lock_stack); |
|
355 nss_thread_rwlock_alloc_failed = 1; |
|
356 return; |
|
357 } |
|
358 } else { |
|
359 nss_thread_rwlock_alloc_failed = 1; |
|
360 return; |
|
361 } |
|
362 } |
|
363 /* |
|
364 * add rwlock to lock stack, if limit is not exceeded |
|
365 */ |
|
366 if (lock_stack) { |
|
367 if (lock_stack->trs_index < _NSS_RWLOCK_RANK_ORDER_LIMIT) |
|
368 lock_stack->trs_stack[lock_stack->trs_index++] = rwlock; |
|
369 } |
|
370 nss_thread_rwlock_initialized = 1; |
|
371 } |
|
372 |
|
373 static void |
|
374 nssRWLock_ReleaseLockStack(void *lock_stack) |
|
375 { |
|
376 PR_ASSERT(lock_stack); |
|
377 PR_DELETE(lock_stack); |
|
378 } |
|
379 |
|
380 /* |
|
381 * nssRWLock_GetThreadRank |
|
382 * |
|
383 * return thread's lock rank. If thread-private-data for the lock |
|
384 * stack is not allocated, return NSS_RWLOCK_RANK_NONE. |
|
385 */ |
|
386 |
|
387 static PRUint32 |
|
388 nssRWLock_GetThreadRank(PRThread *me) |
|
389 { |
|
390 thread_rwlock_stack *lock_stack; |
|
391 |
|
392 if (nss_thread_rwlock_initialized) { |
|
393 if ((lock_stack = PR_GetThreadPrivate(nss_thread_rwlock)) == NULL) |
|
394 return (NSS_RWLOCK_RANK_NONE); |
|
395 else |
|
396 return(lock_stack->trs_stack[lock_stack->trs_index - 1]->rw_rank); |
|
397 |
|
398 } else |
|
399 return (NSS_RWLOCK_RANK_NONE); |
|
400 } |
|
401 |
|
402 /* |
|
403 * nssRWLock_UnsetThreadRank |
|
404 * |
|
405 * remove the rwlock from the lock stack. Since locks may not be |
|
406 * unlocked in a FIFO order, the entire lock stack is searched. |
|
407 */ |
|
408 |
|
409 static void |
|
410 nssRWLock_UnsetThreadRank(PRThread *me, NSSRWLock *rwlock) |
|
411 { |
|
412 thread_rwlock_stack *lock_stack; |
|
413 int new_index = 0, index, done = 0; |
|
414 |
|
415 if (!nss_thread_rwlock_initialized) |
|
416 return; |
|
417 |
|
418 lock_stack = PR_GetThreadPrivate(nss_thread_rwlock); |
|
419 |
|
420 PR_ASSERT(lock_stack != NULL); |
|
421 |
|
422 index = lock_stack->trs_index - 1; |
|
423 while (index-- >= 0) { |
|
424 if ((lock_stack->trs_stack[index] == rwlock) && !done) { |
|
425 /* |
|
426 * reset the slot for rwlock |
|
427 */ |
|
428 lock_stack->trs_stack[index] = NULL; |
|
429 done = 1; |
|
430 } |
|
431 /* |
|
432 * search for the lowest-numbered empty slot, above which there are |
|
433 * no non-empty slots |
|
434 */ |
|
435 if ((lock_stack->trs_stack[index] != NULL) && !new_index) |
|
436 new_index = index + 1; |
|
437 if (done && new_index) |
|
438 break; |
|
439 } |
|
440 /* |
|
441 * set top of stack to highest numbered empty slot |
|
442 */ |
|
443 lock_stack->trs_index = new_index; |
|
444 |
|
445 } |
|
446 |
|
447 #endif /* NSS_RWLOCK_RANK_ORDER_DEBUG */ |