/src/nss-nspr/nss/lib/util/nssrwlk.c
Line | Count | Source (jump to first uncovered line) |
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 | 232 | #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 | 6 | { |
75 | 6 | NSSRWLock *rwlock; |
76 | | |
77 | 6 | rwlock = PR_NEWZAP(NSSRWLock); |
78 | 6 | if (rwlock == NULL) |
79 | 0 | return NULL; |
80 | | |
81 | 6 | rwlock->rw_lock = PZ_NewLock(nssILockRWLock); |
82 | 6 | if (rwlock->rw_lock == NULL) { |
83 | 0 | goto loser; |
84 | 0 | } |
85 | 6 | rwlock->rw_reader_waitq = PZ_NewCondVar(rwlock->rw_lock); |
86 | 6 | if (rwlock->rw_reader_waitq == NULL) { |
87 | 0 | goto loser; |
88 | 0 | } |
89 | 6 | rwlock->rw_writer_waitq = PZ_NewCondVar(rwlock->rw_lock); |
90 | 6 | if (rwlock->rw_writer_waitq == NULL) { |
91 | 0 | goto loser; |
92 | 0 | } |
93 | 6 | if (lock_name != NULL) { |
94 | 6 | rwlock->rw_name = (char *)PR_Malloc((PRUint32)strlen(lock_name) + 1); |
95 | 6 | if (rwlock->rw_name == NULL) { |
96 | 0 | goto loser; |
97 | 0 | } |
98 | 6 | strcpy(rwlock->rw_name, lock_name); |
99 | 6 | } else { |
100 | 0 | rwlock->rw_name = NULL; |
101 | 0 | } |
102 | 6 | rwlock->rw_rank = lock_rank; |
103 | 6 | rwlock->rw_waiting_readers = 0; |
104 | 6 | rwlock->rw_waiting_writers = 0; |
105 | 6 | rwlock->rw_reader_locks = 0; |
106 | 6 | rwlock->rw_writer_locks = 0; |
107 | | |
108 | 6 | return rwlock; |
109 | | |
110 | 0 | loser: |
111 | 0 | NSSRWLock_Destroy(rwlock); |
112 | 0 | return (NULL); |
113 | 6 | } |
114 | | |
115 | | /* |
116 | | ** Destroy the given RWLock "lock". |
117 | | */ |
118 | | void |
119 | | NSSRWLock_Destroy(NSSRWLock *rwlock) |
120 | 0 | { |
121 | 0 | PR_ASSERT(rwlock != NULL); |
122 | 0 | PR_ASSERT(rwlock->rw_waiting_readers == 0); |
123 | 0 | PR_ASSERT(rwlock->rw_writer_locks == 0); |
124 | 0 | PR_ASSERT(rwlock->rw_reader_locks == 0); |
125 | | |
126 | | /* XXX Shouldn't we lock the PZLock before destroying this?? */ |
127 | |
|
128 | 0 | if (rwlock->rw_name) |
129 | 0 | PR_Free(rwlock->rw_name); |
130 | 0 | if (rwlock->rw_reader_waitq) |
131 | 0 | PZ_DestroyCondVar(rwlock->rw_reader_waitq); |
132 | 0 | if (rwlock->rw_writer_waitq) |
133 | 0 | PZ_DestroyCondVar(rwlock->rw_writer_waitq); |
134 | 0 | if (rwlock->rw_lock) |
135 | 0 | PZ_DestroyLock(rwlock->rw_lock); |
136 | 0 | PR_DELETE(rwlock); |
137 | 0 | } |
138 | | |
139 | | /* |
140 | | ** Read-lock the RWLock. |
141 | | */ |
142 | | void |
143 | | NSSRWLock_LockRead(NSSRWLock *rwlock) |
144 | 51 | { |
145 | 51 | PRThread *me = PR_GetCurrentThread(); |
146 | | |
147 | 51 | 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 | 51 | UNTIL((rwlock->rw_owner == me) || /* I own it, or */ |
162 | 51 | ((rwlock->rw_owner == NULL) && /* no-one owns it, and */ |
163 | 51 | (rwlock->rw_waiting_writers == 0))) |
164 | 0 | { /* no-one is waiting to own */ |
165 | |
|
166 | 0 | rwlock->rw_waiting_readers++; |
167 | 0 | PZ_WaitCondVar(rwlock->rw_reader_waitq, PR_INTERVAL_NO_TIMEOUT); |
168 | 0 | rwlock->rw_waiting_readers--; |
169 | 0 | } |
170 | 51 | rwlock->rw_reader_locks++; /* Increment read-lock count */ |
171 | | |
172 | 51 | 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 | 51 | } |
178 | | |
179 | | /* Unlock a Read lock held on this RW lock. |
180 | | */ |
181 | | void |
182 | | NSSRWLock_UnlockRead(NSSRWLock *rwlock) |
183 | 51 | { |
184 | 51 | PZ_Lock(rwlock->rw_lock); |
185 | | |
186 | 51 | PR_ASSERT(rwlock->rw_reader_locks > 0); /* lock must be read locked */ |
187 | | |
188 | 51 | if ((rwlock->rw_reader_locks > 0) && /* caller isn't screwey */ |
189 | 51 | (--rwlock->rw_reader_locks == 0) && /* not read locked any more */ |
190 | 51 | (rwlock->rw_owner == NULL) && /* not write locked */ |
191 | 51 | (rwlock->rw_waiting_writers > 0)) { /* someone's waiting. */ |
192 | |
|
193 | 0 | PZ_NotifyCondVar(rwlock->rw_writer_waitq); /* wake him up. */ |
194 | 0 | } |
195 | | |
196 | 51 | 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 | 51 | return; |
205 | 51 | } |
206 | | |
207 | | /* |
208 | | ** Write-lock the RWLock. |
209 | | */ |
210 | | void |
211 | | NSSRWLock_LockWrite(NSSRWLock *rwlock) |
212 | 10 | { |
213 | 10 | PRThread *me = PR_GetCurrentThread(); |
214 | | |
215 | 10 | 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 | 10 | PR_ASSERT(rwlock->rw_reader_locks >= 0); |
229 | 10 | PR_ASSERT(me != NULL); |
230 | | |
231 | 10 | UNTIL((rwlock->rw_owner == me) || /* I own write lock, or */ |
232 | 10 | ((rwlock->rw_owner == NULL) && /* no writer and */ |
233 | 10 | (rwlock->rw_reader_locks == 0))) |
234 | 0 | { /* no readers, either. */ |
235 | |
|
236 | 0 | rwlock->rw_waiting_writers++; |
237 | 0 | PZ_WaitCondVar(rwlock->rw_writer_waitq, PR_INTERVAL_NO_TIMEOUT); |
238 | 0 | rwlock->rw_waiting_writers--; |
239 | 0 | PR_ASSERT(rwlock->rw_reader_locks >= 0); |
240 | 0 | } |
241 | | |
242 | 10 | PR_ASSERT(rwlock->rw_reader_locks == 0); |
243 | | /* |
244 | | * apply write lock |
245 | | */ |
246 | 10 | rwlock->rw_owner = me; |
247 | 10 | rwlock->rw_writer_locks++; /* Increment write-lock count */ |
248 | | |
249 | 10 | 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 | 10 | } |
258 | | |
259 | | /* Unlock a Read lock held on this RW lock. |
260 | | */ |
261 | | void |
262 | | NSSRWLock_UnlockWrite(NSSRWLock *rwlock) |
263 | 10 | { |
264 | 10 | PRThread *me = PR_GetCurrentThread(); |
265 | | |
266 | 10 | PZ_Lock(rwlock->rw_lock); |
267 | 10 | PR_ASSERT(rwlock->rw_owner == me); /* lock must be write-locked by me. */ |
268 | 10 | PR_ASSERT(rwlock->rw_writer_locks > 0); /* lock must be write locked */ |
269 | | |
270 | 10 | if (rwlock->rw_owner == me && /* I own it, and */ |
271 | 10 | rwlock->rw_writer_locks > 0 && /* I own it, and */ |
272 | 10 | --rwlock->rw_writer_locks == 0) { /* I'm all done with it */ |
273 | | |
274 | 6 | rwlock->rw_owner = NULL; /* I don't own it any more. */ |
275 | | |
276 | | /* Give preference to waiting writers. */ |
277 | 6 | if (rwlock->rw_waiting_writers > 0) { |
278 | 0 | if (rwlock->rw_reader_locks == 0) |
279 | 0 | PZ_NotifyCondVar(rwlock->rw_writer_waitq); |
280 | 6 | } else if (rwlock->rw_waiting_readers > 0) { |
281 | 0 | PZ_NotifyAllCondVar(rwlock->rw_reader_waitq); |
282 | 0 | } |
283 | 6 | } |
284 | 10 | 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 | 10 | return; |
293 | 10 | } |
294 | | |
295 | | /* This is primarily for debugging, i.e. for inclusion in ASSERT calls. */ |
296 | | PRBool |
297 | | NSSRWLock_HaveWriteLock(NSSRWLock *rwlock) |
298 | 0 | { |
299 | 0 | PRBool ownWriteLock; |
300 | 0 | 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 | 0 | ownWriteLock = (PRBool)(me == rwlock->rw_owner); |
312 | | #if UNNECESSARY |
313 | | PZ_Unlock(rwlock->rw_lock); |
314 | | #endif |
315 | 0 | return ownWriteLock; |
316 | 0 | } |
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 */ |