pthread_rwlock.c revision 1.41 1 /* $NetBSD: pthread_rwlock.c,v 1.41 2020/06/01 11:44:59 ad Exp $ */
2
3 /*-
4 * Copyright (c) 2002, 2006, 2007, 2008, 2020 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Nathan J. Williams, by Jason R. Thorpe, and by Andrew Doran.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: pthread_rwlock.c,v 1.41 2020/06/01 11:44:59 ad Exp $");
34
35 #include <sys/types.h>
36 #include <sys/lwpctl.h>
37
38 #include <assert.h>
39 #include <time.h>
40 #include <errno.h>
41 #include <stddef.h>
42
43 #include "pthread.h"
44 #include "pthread_int.h"
45 #include "reentrant.h"
46
47 #define _RW_LOCKED 0
48 #define _RW_WANT_WRITE 1
49 #define _RW_WANT_READ 2
50
51 #if __GNUC_PREREQ__(3, 0)
52 #define NOINLINE __attribute ((noinline))
53 #else
54 #define NOINLINE /* nothing */
55 #endif
56
57 static int pthread__rwlock_wrlock(pthread_rwlock_t *, const struct timespec *);
58 static int pthread__rwlock_rdlock(pthread_rwlock_t *, const struct timespec *);
59 static void pthread__rwlock_early(pthread_t, pthread_rwlock_t *,
60 pthread_mutex_t *);
61
62 int _pthread_rwlock_held_np(pthread_rwlock_t *);
63 int _pthread_rwlock_rdheld_np(pthread_rwlock_t *);
64 int _pthread_rwlock_wrheld_np(pthread_rwlock_t *);
65
66 #ifndef lint
67 __weak_alias(pthread_rwlock_held_np,_pthread_rwlock_held_np)
68 __weak_alias(pthread_rwlock_rdheld_np,_pthread_rwlock_rdheld_np)
69 __weak_alias(pthread_rwlock_wrheld_np,_pthread_rwlock_wrheld_np)
70 #endif
71
72 __strong_alias(__libc_rwlock_init,pthread_rwlock_init)
73 __strong_alias(__libc_rwlock_rdlock,pthread_rwlock_rdlock)
74 __strong_alias(__libc_rwlock_wrlock,pthread_rwlock_wrlock)
75 __strong_alias(__libc_rwlock_tryrdlock,pthread_rwlock_tryrdlock)
76 __strong_alias(__libc_rwlock_trywrlock,pthread_rwlock_trywrlock)
77 __strong_alias(__libc_rwlock_unlock,pthread_rwlock_unlock)
78 __strong_alias(__libc_rwlock_destroy,pthread_rwlock_destroy)
79
80 static inline uintptr_t
81 rw_cas(pthread_rwlock_t *ptr, uintptr_t o, uintptr_t n)
82 {
83
84 return (uintptr_t)atomic_cas_ptr(&ptr->ptr_owner, (void *)o,
85 (void *)n);
86 }
87
88 int
89 pthread_rwlock_init(pthread_rwlock_t *ptr,
90 const pthread_rwlockattr_t *attr)
91 {
92 if (__predict_false(__uselibcstub))
93 return __libc_rwlock_init_stub(ptr, attr);
94
95 pthread__error(EINVAL, "Invalid rwlock attribute",
96 attr == NULL || attr->ptra_magic == _PT_RWLOCKATTR_MAGIC);
97
98 ptr->ptr_magic = _PT_RWLOCK_MAGIC;
99 PTQ_INIT(&ptr->ptr_rblocked);
100 PTQ_INIT(&ptr->ptr_wblocked);
101 ptr->ptr_nreaders = 0;
102 ptr->ptr_owner = NULL;
103
104 return 0;
105 }
106
107
108 int
109 pthread_rwlock_destroy(pthread_rwlock_t *ptr)
110 {
111 if (__predict_false(__uselibcstub))
112 return __libc_rwlock_destroy_stub(ptr);
113
114 pthread__error(EINVAL, "Invalid rwlock",
115 ptr->ptr_magic == _PT_RWLOCK_MAGIC);
116
117 if ((!PTQ_EMPTY(&ptr->ptr_rblocked)) ||
118 (!PTQ_EMPTY(&ptr->ptr_wblocked)) ||
119 (ptr->ptr_nreaders != 0) ||
120 (ptr->ptr_owner != NULL))
121 return EINVAL;
122 ptr->ptr_magic = _PT_RWLOCK_DEAD;
123
124 return 0;
125 }
126
127 /* We want function call overhead. */
128 NOINLINE static void
129 pthread__rwlock_pause(void)
130 {
131
132 pthread__smt_pause();
133 }
134
135 NOINLINE static int
136 pthread__rwlock_spin(uintptr_t owner)
137 {
138 pthread_t thread;
139 unsigned int i;
140
141 if ((owner & ~RW_THREAD) != RW_WRITE_LOCKED)
142 return 0;
143
144 thread = (pthread_t)(owner & RW_THREAD);
145 if (__predict_false(thread == NULL) ||
146 thread->pt_lwpctl->lc_curcpu == LWPCTL_CPU_NONE)
147 return 0;
148
149 for (i = 128; i != 0; i--)
150 pthread__rwlock_pause();
151 return 1;
152 }
153
154 static int
155 pthread__rwlock_rdlock(pthread_rwlock_t *ptr, const struct timespec *ts)
156 {
157 uintptr_t owner, next;
158 pthread_mutex_t *interlock;
159 pthread_t self;
160 int error;
161
162 pthread__error(EINVAL, "Invalid rwlock",
163 ptr->ptr_magic == _PT_RWLOCK_MAGIC);
164
165 for (owner = (uintptr_t)ptr->ptr_owner;; owner = next) {
166 /*
167 * Read the lock owner field. If the need-to-wait
168 * indicator is clear, then try to acquire the lock.
169 */
170 if ((owner & (RW_WRITE_LOCKED | RW_WRITE_WANTED)) == 0) {
171 next = rw_cas(ptr, owner, owner + RW_READ_INCR);
172 if (owner == next) {
173 /* Got it! */
174 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
175 membar_enter();
176 #endif
177 return 0;
178 }
179
180 /*
181 * Didn't get it -- spin around again (we'll
182 * probably sleep on the next iteration).
183 */
184 continue;
185 }
186
187 self = pthread__self();
188 if ((owner & RW_THREAD) == (uintptr_t)self)
189 return EDEADLK;
190
191 /* If held write locked and no waiters, spin. */
192 if (pthread__rwlock_spin(owner)) {
193 while (pthread__rwlock_spin(owner)) {
194 owner = (uintptr_t)ptr->ptr_owner;
195 }
196 next = owner;
197 continue;
198 }
199
200 /*
201 * Grab the interlock. Once we have that, we
202 * can adjust the waiter bits and sleep queue.
203 */
204 interlock = pthread__hashlock(ptr);
205 pthread_mutex_lock(interlock);
206
207 /*
208 * Mark the rwlock as having waiters. If the set fails,
209 * then we may not need to sleep and should spin again.
210 */
211 next = rw_cas(ptr, owner, owner | RW_HAS_WAITERS);
212 if (owner != next) {
213 pthread_mutex_unlock(interlock);
214 continue;
215 }
216
217 /* The waiters bit is set - it's safe to sleep. */
218 PTQ_INSERT_HEAD(&ptr->ptr_rblocked, self, pt_sleep);
219 ptr->ptr_nreaders++;
220 self->pt_rwlocked = _RW_WANT_READ;
221 self->pt_sleepobj = &ptr->ptr_rblocked;
222 error = pthread__park(self, interlock, &ptr->ptr_rblocked,
223 ts, 0);
224
225 if (self->pt_sleepobj != NULL) {
226 pthread__rwlock_early(self, ptr, interlock);
227 }
228
229 /* Did we get the lock? */
230 if (self->pt_rwlocked == _RW_LOCKED) {
231 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
232 membar_enter();
233 #endif
234 return 0;
235 }
236 if (error != 0)
237 return error;
238
239 pthread__errorfunc(__FILE__, __LINE__, __func__,
240 "direct handoff failure");
241 }
242 }
243
244
245 int
246 pthread_rwlock_tryrdlock(pthread_rwlock_t *ptr)
247 {
248 uintptr_t owner, next;
249
250 if (__predict_false(__uselibcstub))
251 return __libc_rwlock_tryrdlock_stub(ptr);
252
253 pthread__error(EINVAL, "Invalid rwlock",
254 ptr->ptr_magic == _PT_RWLOCK_MAGIC);
255
256 /*
257 * Don't get a readlock if there is a writer or if there are waiting
258 * writers; i.e. prefer writers to readers. This strategy is dictated
259 * by SUSv3.
260 */
261 for (owner = (uintptr_t)ptr->ptr_owner;; owner = next) {
262 if ((owner & (RW_WRITE_LOCKED | RW_WRITE_WANTED)) != 0)
263 return EBUSY;
264 next = rw_cas(ptr, owner, owner + RW_READ_INCR);
265 if (owner == next) {
266 /* Got it! */
267 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
268 membar_enter();
269 #endif
270 return 0;
271 }
272 }
273 }
274
275 static int
276 pthread__rwlock_wrlock(pthread_rwlock_t *ptr, const struct timespec *ts)
277 {
278 uintptr_t owner, next;
279 pthread_mutex_t *interlock;
280 pthread_t self;
281 int error;
282
283 self = pthread__self();
284 _DIAGASSERT(((uintptr_t)self & RW_FLAGMASK) == 0);
285
286 pthread__error(EINVAL, "Invalid rwlock",
287 ptr->ptr_magic == _PT_RWLOCK_MAGIC);
288
289 for (owner = (uintptr_t)ptr->ptr_owner;; owner = next) {
290 /*
291 * Read the lock owner field. If the need-to-wait
292 * indicator is clear, then try to acquire the lock.
293 */
294 if ((owner & RW_THREAD) == 0) {
295 next = rw_cas(ptr, owner,
296 (uintptr_t)self | RW_WRITE_LOCKED);
297 if (owner == next) {
298 /* Got it! */
299 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
300 membar_enter();
301 #endif
302 return 0;
303 }
304
305 /*
306 * Didn't get it -- spin around again (we'll
307 * probably sleep on the next iteration).
308 */
309 continue;
310 }
311
312 if ((owner & RW_THREAD) == (uintptr_t)self)
313 return EDEADLK;
314
315 /* If held write locked and no waiters, spin. */
316 if (pthread__rwlock_spin(owner)) {
317 while (pthread__rwlock_spin(owner)) {
318 owner = (uintptr_t)ptr->ptr_owner;
319 }
320 next = owner;
321 continue;
322 }
323
324 /*
325 * Grab the interlock. Once we have that, we
326 * can adjust the waiter bits and sleep queue.
327 */
328 interlock = pthread__hashlock(ptr);
329 pthread_mutex_lock(interlock);
330
331 /*
332 * Mark the rwlock as having waiters. If the set fails,
333 * then we may not need to sleep and should spin again.
334 */
335 next = rw_cas(ptr, owner,
336 owner | RW_HAS_WAITERS | RW_WRITE_WANTED);
337 if (owner != next) {
338 pthread_mutex_unlock(interlock);
339 continue;
340 }
341
342 /* The waiters bit is set - it's safe to sleep. */
343 PTQ_INSERT_TAIL(&ptr->ptr_wblocked, self, pt_sleep);
344 self->pt_rwlocked = _RW_WANT_WRITE;
345 self->pt_sleepobj = &ptr->ptr_wblocked;
346 error = pthread__park(self, interlock, &ptr->ptr_wblocked,
347 ts, 0);
348
349 if (self->pt_sleepobj != NULL) {
350 pthread__rwlock_early(self, ptr, interlock);
351 }
352
353 /* Did we get the lock? */
354 if (self->pt_rwlocked == _RW_LOCKED) {
355 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
356 membar_enter();
357 #endif
358 return 0;
359 }
360 if (error != 0)
361 return error;
362
363 pthread__errorfunc(__FILE__, __LINE__, __func__,
364 "direct handoff failure");
365 }
366 }
367
368 int
369 pthread_rwlock_trywrlock(pthread_rwlock_t *ptr)
370 {
371 uintptr_t owner, next;
372 pthread_t self;
373
374 if (__predict_false(__uselibcstub))
375 return __libc_rwlock_trywrlock_stub(ptr);
376
377 pthread__error(EINVAL, "Invalid rwlock",
378 ptr->ptr_magic == _PT_RWLOCK_MAGIC);
379
380 self = pthread__self();
381 _DIAGASSERT(((uintptr_t)self & RW_FLAGMASK) == 0);
382
383 for (owner = (uintptr_t)ptr->ptr_owner;; owner = next) {
384 if (owner != 0)
385 return EBUSY;
386 next = rw_cas(ptr, owner, (uintptr_t)self | RW_WRITE_LOCKED);
387 if (owner == next) {
388 /* Got it! */
389 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
390 membar_enter();
391 #endif
392 return 0;
393 }
394 }
395 }
396
397 int
398 pthread_rwlock_rdlock(pthread_rwlock_t *ptr)
399 {
400 if (__predict_false(__uselibcstub))
401 return __libc_rwlock_rdlock_stub(ptr);
402
403 return pthread__rwlock_rdlock(ptr, NULL);
404 }
405
406 int
407 pthread_rwlock_timedrdlock(pthread_rwlock_t *ptr,
408 const struct timespec *abs_timeout)
409 {
410 if (abs_timeout == NULL)
411 return EINVAL;
412 if ((abs_timeout->tv_nsec >= 1000000000) ||
413 (abs_timeout->tv_nsec < 0) ||
414 (abs_timeout->tv_sec < 0))
415 return EINVAL;
416
417 return pthread__rwlock_rdlock(ptr, abs_timeout);
418 }
419
420 int
421 pthread_rwlock_wrlock(pthread_rwlock_t *ptr)
422 {
423 if (__predict_false(__uselibcstub))
424 return __libc_rwlock_wrlock_stub(ptr);
425
426 return pthread__rwlock_wrlock(ptr, NULL);
427 }
428
429 int
430 pthread_rwlock_timedwrlock(pthread_rwlock_t *ptr,
431 const struct timespec *abs_timeout)
432 {
433 if (abs_timeout == NULL)
434 return EINVAL;
435 if ((abs_timeout->tv_nsec >= 1000000000) ||
436 (abs_timeout->tv_nsec < 0) ||
437 (abs_timeout->tv_sec < 0))
438 return EINVAL;
439
440 return pthread__rwlock_wrlock(ptr, abs_timeout);
441 }
442
443
444 int
445 pthread_rwlock_unlock(pthread_rwlock_t *ptr)
446 {
447 uintptr_t owner, decr, new, next;
448 pthread_mutex_t *interlock;
449 pthread_t self, thread;
450
451 if (__predict_false(__uselibcstub))
452 return __libc_rwlock_unlock_stub(ptr);
453
454 pthread__error(EINVAL, "Invalid rwlock",
455 ptr->ptr_magic == _PT_RWLOCK_MAGIC);
456
457 #ifndef PTHREAD__ATOMIC_IS_MEMBAR
458 membar_exit();
459 #endif
460
461 /*
462 * Since we used an add operation to set the required lock
463 * bits, we can use a subtract to clear them, which makes
464 * the read-release and write-release path similar.
465 */
466 owner = (uintptr_t)ptr->ptr_owner;
467 if ((owner & RW_WRITE_LOCKED) != 0) {
468 self = pthread__self();
469 decr = (uintptr_t)self | RW_WRITE_LOCKED;
470 if ((owner & RW_THREAD) != (uintptr_t)self) {
471 return EPERM;
472 }
473 } else {
474 decr = RW_READ_INCR;
475 if (owner == 0) {
476 return EPERM;
477 }
478 }
479
480 for (;; owner = next) {
481 /*
482 * Compute what we expect the new value of the lock to be.
483 * Only proceed to do direct handoff if there are waiters,
484 * and if the lock would become unowned.
485 */
486 new = (owner - decr);
487 if ((new & (RW_THREAD | RW_HAS_WAITERS)) != RW_HAS_WAITERS) {
488 next = rw_cas(ptr, owner, new);
489 if (owner == next) {
490 /* Released! */
491 return 0;
492 }
493 continue;
494 }
495
496 /*
497 * Grab the interlock. Once we have that, we can adjust
498 * the waiter bits. We must check to see if there are
499 * still waiters before proceeding.
500 */
501 interlock = pthread__hashlock(ptr);
502 pthread_mutex_lock(interlock);
503 owner = (uintptr_t)ptr->ptr_owner;
504 if ((owner & RW_HAS_WAITERS) == 0) {
505 pthread_mutex_unlock(interlock);
506 next = owner;
507 continue;
508 }
509
510 /*
511 * Give the lock away. SUSv3 dictates that we must give
512 * preference to writers.
513 */
514 self = pthread__self();
515 if ((thread = PTQ_FIRST(&ptr->ptr_wblocked)) != NULL) {
516 _DIAGASSERT(((uintptr_t)thread & RW_FLAGMASK) == 0);
517 new = (uintptr_t)thread | RW_WRITE_LOCKED;
518
519 if (PTQ_NEXT(thread, pt_sleep) != NULL)
520 new |= RW_HAS_WAITERS | RW_WRITE_WANTED;
521 else if (ptr->ptr_nreaders != 0)
522 new |= RW_HAS_WAITERS;
523
524 /*
525 * Set in the new value. The lock becomes owned
526 * by the writer that we are about to wake.
527 */
528 (void)atomic_swap_ptr(&ptr->ptr_owner, (void *)new);
529
530 /* Wake the writer. */
531 thread->pt_rwlocked = _RW_LOCKED;
532 pthread__unpark(&ptr->ptr_wblocked, self,
533 interlock);
534 } else {
535 new = 0;
536 PTQ_FOREACH(thread, &ptr->ptr_rblocked, pt_sleep) {
537 /*
538 * May have already been handed the lock,
539 * since pthread__unpark_all() can release
540 * our interlock before awakening all
541 * threads.
542 */
543 if (thread->pt_sleepobj == NULL)
544 continue;
545 new += RW_READ_INCR;
546 thread->pt_rwlocked = _RW_LOCKED;
547 }
548
549 /*
550 * Set in the new value. The lock becomes owned
551 * by the readers that we are about to wake.
552 */
553 (void)atomic_swap_ptr(&ptr->ptr_owner, (void *)new);
554
555 /* Wake up all sleeping readers. */
556 ptr->ptr_nreaders = 0;
557 pthread__unpark_all(&ptr->ptr_rblocked, self,
558 interlock);
559 }
560 pthread_mutex_unlock(interlock);
561
562 return 0;
563 }
564 }
565
566 /*
567 * Called when a timedlock awakens early to adjust the waiter bits.
568 * The rwlock's interlock is held on entry, and the caller has been
569 * removed from the waiters lists.
570 */
571 static void
572 pthread__rwlock_early(pthread_t self, pthread_rwlock_t *ptr,
573 pthread_mutex_t *interlock)
574 {
575 uintptr_t owner, set, newval, next;
576 pthread_queue_t *queue;
577
578 pthread_mutex_lock(interlock);
579 if ((queue = self->pt_sleepobj) == NULL) {
580 pthread_mutex_unlock(interlock);
581 return;
582 }
583 PTQ_REMOVE(queue, self, pt_sleep);
584 self->pt_sleepobj = NULL;
585 owner = (uintptr_t)ptr->ptr_owner;
586
587 if ((owner & RW_THREAD) == 0) {
588 pthread__errorfunc(__FILE__, __LINE__, __func__,
589 "lock not held");
590 }
591
592 if (!PTQ_EMPTY(&ptr->ptr_wblocked))
593 set = RW_HAS_WAITERS | RW_WRITE_WANTED;
594 else if (ptr->ptr_nreaders != 0)
595 set = RW_HAS_WAITERS;
596 else
597 set = 0;
598
599 for (;; owner = next) {
600 newval = (owner & ~(RW_HAS_WAITERS | RW_WRITE_WANTED)) | set;
601 next = rw_cas(ptr, owner, newval);
602 if (owner == next)
603 break;
604 }
605 pthread_mutex_unlock(interlock);
606 }
607
608 int
609 _pthread_rwlock_held_np(pthread_rwlock_t *ptr)
610 {
611 uintptr_t owner = (uintptr_t)ptr->ptr_owner;
612
613 if ((owner & RW_WRITE_LOCKED) != 0)
614 return (owner & RW_THREAD) == (uintptr_t)pthread__self();
615 return (owner & RW_THREAD) != 0;
616 }
617
618 int
619 _pthread_rwlock_rdheld_np(pthread_rwlock_t *ptr)
620 {
621 uintptr_t owner = (uintptr_t)ptr->ptr_owner;
622
623 return (owner & RW_THREAD) != 0 && (owner & RW_WRITE_LOCKED) == 0;
624 }
625
626 int
627 _pthread_rwlock_wrheld_np(pthread_rwlock_t *ptr)
628 {
629 uintptr_t owner = (uintptr_t)ptr->ptr_owner;
630
631 return (owner & (RW_THREAD | RW_WRITE_LOCKED)) ==
632 ((uintptr_t)pthread__self() | RW_WRITE_LOCKED);
633 }
634
635 #ifdef _PTHREAD_PSHARED
636 int
637 pthread_rwlockattr_getpshared(const pthread_rwlockattr_t * __restrict attr,
638 int * __restrict pshared)
639 {
640
641 pthread__error(EINVAL, "Invalid rwlock attribute",
642 ptr->ptra_magic == _PT_RWLOCKATTR_MAGIC);
643
644 *pshared = PTHREAD_PROCESS_PRIVATE;
645 return 0;
646 }
647
648 int
649 pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)
650 {
651
652 pthread__error(EINVAL, "Invalid rwlock attribute",
653 ptr->ptra_magic == _PT_RWLOCKATTR_MAGIC);
654
655 switch(pshared) {
656 case PTHREAD_PROCESS_PRIVATE:
657 return 0;
658 case PTHREAD_PROCESS_SHARED:
659 return ENOSYS;
660 }
661 return EINVAL;
662 }
663 #endif
664
665 int
666 pthread_rwlockattr_init(pthread_rwlockattr_t *attr)
667 {
668
669 if (attr == NULL)
670 return EINVAL;
671 attr->ptra_magic = _PT_RWLOCKATTR_MAGIC;
672
673 return 0;
674 }
675
676
677 int
678 pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)
679 {
680
681 pthread__error(EINVAL, "Invalid rwlock attribute",
682 attr->ptra_magic == _PT_RWLOCKATTR_MAGIC);
683
684 attr->ptra_magic = _PT_RWLOCKATTR_DEAD;
685
686 return 0;
687 }
688