Coverage Report

Created: 2024-11-21 07:03

/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 */