xref: /illumos-gate/usr/src/lib/libnisdb/nisdb_rw.c (revision a87701e9837f8a9ee9e4c4d3186295c0e29f743f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2015 Gary Mills
24  * Copyright (c) 2001 by Sun Microsystems, Inc.
25  * All rights reserved.
26  */
27 
28 #include <stdio.h>
29 #include <rpc/types.h>
30 #include <rpc/xdr.h>
31 #include "db_dictionary_c.h"
32 #include "nisdb_rw.h"
33 #include "nisdb_ldap.h"
34 
35 /*
36  * Nesting-safe RW locking functions. Return 0 when successful, an
37  * error number from the E-series when not.
38  */
39 
40 int
__nisdb_rwinit(__nisdb_rwlock_t * rw)41 __nisdb_rwinit(__nisdb_rwlock_t *rw) {
42 
43 	int	ret;
44 
45 	if (rw == 0) {
46 #ifdef	NISDB_MT_DEBUG
47 		abort();
48 #endif	/* NISDB_MT_DEBUG */
49 		return (EFAULT);
50 	}
51 
52 	if ((ret = mutex_init(&rw->mutex, USYNC_THREAD, 0)) != 0)
53 		return (ret);
54 	if ((ret = cond_init(&rw->cv, USYNC_THREAD, 0)) != 0)
55 		return (ret);
56 	rw->destroyed = 0;
57 
58 	/*
59 	 * If we allow read-to-write lock migration, there's a potential
60 	 * race condition if two or more threads want to upgrade at the
61 	 * same time. The simple and safe (but crude and possibly costly)
62 	 * method to fix this is to always use exclusive locks, and so
63 	 * that has to be the default.
64 	 *
65 	 * There are two conditions under which it is safe to set
66 	 * 'force_write' to zero for a certain lock structure:
67 	 *
68 	 * (1)	The lock will never be subject to migration, or
69 	 *
70 	 * (2)	It's OK if the data protected by the lock has changed
71 	 *	(a)	Every time the lock (read or write) has been
72 	 *		acquired (even if the lock already was held by
73 	 *		the thread), and
74 	 *	(b)	After every call to a function that might have
75 	 *		acquired the lock.
76 	 */
77 	rw->force_write = NISDB_FORCE_WRITE;
78 
79 	rw->writer_count = rw->reader_count = rw->reader_blocked = 0;
80 	rw->writer.id = rw->reader.id = INV_PTHREAD_ID;
81 	rw->writer.count = rw->reader.count = 0;
82 	rw->writer.next = rw->reader.next = 0;
83 
84 	return (0);
85 }
86 
87 
88 static __nisdb_rl_t *
find_reader(pthread_t id,__nisdb_rwlock_t * rw)89 find_reader(pthread_t id, __nisdb_rwlock_t *rw) {
90 
91 	__nisdb_rl_t	*rr;
92 
93 	for (rr = &rw->reader; rr != 0; rr = rr->next) {
94 		if (rr->id == INV_PTHREAD_ID) {
95 			rr = 0;
96 			break;
97 		}
98 		if (rr->id == id)
99 			break;
100 	}
101 
102 	return (rr);
103 }
104 
105 
106 int
__nisdb_rw_readlock_ok(__nisdb_rwlock_t * rw)107 __nisdb_rw_readlock_ok(__nisdb_rwlock_t *rw) {
108 	int		ret;
109 
110 	if (rw == 0)
111 		return (EFAULT);
112 
113 	if (rw->destroyed != 0)
114 		return (ESHUTDOWN);
115 
116 	if ((ret = mutex_lock(&rw->mutex)) != 0)
117 		return (ret);
118 
119 	/*
120 	 * Only allow changing 'force_write' when it's really safe; i.e.,
121 	 * the lock hasn't been destroyed, and there are no readers.
122 	 */
123 	if (rw->destroyed == 0 && rw->reader_count == 0) {
124 		rw->force_write = 0;
125 		ret = 0;
126 	} else {
127 		ret = EBUSY;
128 	}
129 
130 	(void) mutex_unlock(&rw->mutex);
131 
132 	return (ret);
133 }
134 
135 
136 int
__nisdb_rw_force_writelock(__nisdb_rwlock_t * rw)137 __nisdb_rw_force_writelock(__nisdb_rwlock_t *rw) {
138 	int		ret;
139 
140 	if (rw == 0 || rw->destroyed != 0)
141 		return (ESHUTDOWN);
142 
143 	if ((ret = mutex_lock(&rw->mutex)) != 0)
144 		return (ret);
145 
146 	/*
147 	 * Only allow changing 'force_write' when it's really safe; i.e.,
148 	 * the lock hasn't been destroyed, and there are no readers.
149 	 */
150 	if (rw->destroyed == 0 && rw->reader_count == 0) {
151 		rw->force_write = 1;
152 		ret = 0;
153 	} else {
154 		ret = EBUSY;
155 	}
156 
157 	(void) mutex_unlock(&rw->mutex);
158 
159 	return (ret);
160 }
161 
162 
163 int
__nisdb_wlock_trylock(__nisdb_rwlock_t * rw,int trylock)164 __nisdb_wlock_trylock(__nisdb_rwlock_t *rw, int trylock) {
165 
166 	int		ret;
167 	pthread_t	myself = pthread_self();
168 	int		all_readers_blocked = 0;
169 	__nisdb_rl_t	*rr = 0;
170 
171 	if (rw == 0) {
172 #ifdef	NISDB_MT_DEBUG
173 		/* This shouldn't happen */
174 		abort();
175 #endif	/* NISDB_MT_DEBUG */
176 		return (EFAULT);
177 	}
178 
179 	if (rw->destroyed != 0)
180 		return (ESHUTDOWN);
181 
182 	if ((ret = mutex_lock(&rw->mutex)) != 0)
183 		return (ret);
184 
185 	if (rw->destroyed != 0) {
186 		(void) mutex_unlock(&rw->mutex);
187 		return (ESHUTDOWN);
188 	}
189 
190 	/* Simplest (and probably most common) case: no readers or writers */
191 	if (rw->reader_count == 0 && rw->writer_count == 0) {
192 		rw->writer_count = 1;
193 		rw->writer.id = myself;
194 		rw->writer.count = 1;
195 		return (mutex_unlock(&rw->mutex));
196 	}
197 
198 	/*
199 	 * Need to know if we're holding a read lock already, and if
200 	 * all other readers are blocked waiting for the mutex.
201 	 */
202 	if (rw->reader_count > 0) {
203 		if ((rr = find_reader(myself, rw)) != 0) {
204 			if (rr->count) {
205 				/*
206 				 * We're already holding a read lock, so
207 				 * if the number of readers equals the number
208 				 * of blocked readers plus one, all other
209 				 * readers are blocked.
210 				 */
211 				if (rw->reader_count ==
212 						(rw->reader_blocked + 1))
213 					all_readers_blocked = 1;
214 			} else {
215 				/*
216 				 * We're not holding a read lock, so the
217 				 * number of readers should equal the number
218 				 * of blocked readers if all readers are
219 				 * blocked.
220 				 */
221 				if (rw->reader_count == rw->reader_blocked)
222 					all_readers_blocked = 1;
223 			}
224 		}
225 	}
226 
227 	/* Wait for reader(s) or writer to finish */
228 	while (1) {
229 		/*
230 		 * We can stop looping if one of the following holds:
231 		 *	- No readers, no writers
232 		 *	- No writers (or writer is myself), and one of:
233 		 *		- No readers
234 		 *		- One reader, and it's us
235 		 *		- N readers, but all blocked on the mutex
236 		 */
237 		if (
238 		    (rw->writer_count == 0 && rw->reader_count == 0) ||
239 		    (((rw->writer_count == 0 || rw->writer.id == myself) &&
240 		    (rw->reader_count == 0)) ||
241 		    (rw->reader_count == 1 &&
242 		    rw->reader.id == myself))) {
243 			break;
244 		}
245 		/*
246 		 * Provided that all readers are blocked on the mutex
247 		 * we break a potential dead-lock by acquiring the
248 		 * write lock.
249 		 */
250 		if (all_readers_blocked) {
251 			if (rw->writer_count == 0 || rw->writer.id == myself) {
252 				break;
253 			}
254 		}
255 
256 		/*
257 		 * If 'trylock' is set, tell the caller that we'd have to
258 		 * block to obtain the lock.
259 		 */
260 		if (trylock) {
261 			(void) mutex_unlock(&rw->mutex);
262 			return (EBUSY);
263 		}
264 
265 		/* If we're also a reader, indicate that we're blocking */
266 		if (rr != 0) {
267 			rr->wait = 1;
268 			rw->reader_blocked++;
269 		}
270 		if ((ret = cond_wait(&rw->cv, &rw->mutex)) != 0) {
271 			if (rr != 0) {
272 				rr->wait = 0;
273 				if (rw->reader_blocked > 0)
274 					rw->reader_blocked--;
275 #ifdef	NISDB_MT_DEBUG
276 				else
277 					abort();
278 #endif	/* NISDB_MT_DEBUG */
279 			}
280 			(void) mutex_unlock(&rw->mutex);
281 			return (ret);
282 		}
283 		if (rr != 0) {
284 			rr->wait = 0;
285 			if (rw->reader_blocked > 0)
286 				rw->reader_blocked--;
287 #ifdef	NISDB_MT_DEBUG
288 			else
289 				abort();
290 #endif	/* NISDB_MT_DEBUG */
291 		}
292 	}
293 
294 	/* OK to grab the write lock */
295 	rw->writer.id = myself;
296 	/* Increment lock depth */
297 	rw->writer.count++;
298 	/* Set number of writers (doesn't increase with lock depth) */
299 	if (rw->writer_count == 0)
300 		rw->writer_count = 1;
301 
302 	return (mutex_unlock(&rw->mutex));
303 }
304 
305 int
__nisdb_wlock(__nisdb_rwlock_t * rw)306 __nisdb_wlock(__nisdb_rwlock_t *rw) {
307 	return (__nisdb_wlock_trylock(rw, 0));
308 }
309 
310 
311 static __nisdb_rl_t *
increment_reader(pthread_t id,__nisdb_rwlock_t * rw)312 increment_reader(pthread_t id, __nisdb_rwlock_t *rw) {
313 
314 	__nisdb_rl_t	*rr;
315 
316 	for (rr = &rw->reader; rr != 0; rr = rr->next) {
317 		if (rr->id == id || rr->id == INV_PTHREAD_ID)
318 			break;
319 	}
320 	if (rw->reader_count == 0 && rr == &rw->reader) {
321 		/* No previous reader */
322 		rr->id = id;
323 		rw->reader_count = 1;
324 	} else if (rr == 0) {
325 		if ((rr = malloc(sizeof (__nisdb_rl_t))) == 0)
326 			return (0);
327 		rr->id = id;
328 		rr->count = 0;
329 		/*
330 		 * For insertion simplicity, make it the second item
331 		 * on the list.
332 		 */
333 		rr->next = rw->reader.next;
334 		rw->reader.next = rr;
335 		rw->reader_count++;
336 	}
337 	rr->count++;
338 
339 	return (rr);
340 }
341 
342 
343 int
__nisdb_rlock(__nisdb_rwlock_t * rw)344 __nisdb_rlock(__nisdb_rwlock_t *rw) {
345 
346 	int		ret;
347 	pthread_t	myself = pthread_self();
348 	__nisdb_rl_t	*rr;
349 
350 	if (rw == 0) {
351 #ifdef	NISDB_MT_DEBUG
352 		/* This shouldn't happen */
353 		abort();
354 #endif	/* NISDB_MT_DEBUG */
355 		return (EFAULT);
356 	}
357 
358 	if (rw->destroyed != 0)
359 		return (ESHUTDOWN);
360 
361 	if (rw->force_write)
362 		return (__nisdb_wlock(rw));
363 
364 	if ((ret = mutex_lock(&rw->mutex)) != 0)
365 		return (ret);
366 
367 	if (rw->destroyed != 0) {
368 		(void) mutex_unlock(&rw->mutex);
369 		return (ESHUTDOWN);
370 	}
371 
372 	rr = find_reader(myself, rw);
373 
374 	/* Wait for writer to complete; writer == myself also OK */
375 	while (rw->writer_count > 0 && rw->writer.id != myself) {
376 		if (rr != 0) {
377 			rr->wait = 1;
378 			rw->reader_blocked++;
379 		}
380 		if ((ret = cond_wait(&rw->cv, &rw->mutex)) != 0) {
381 			if (rr != 0) {
382 				rr->wait = 0;
383 				if (rw->reader_blocked > 0)
384 					rw->reader_blocked--;
385 #ifdef	NISDB_MT_DEBUG
386 				else
387 					abort();
388 #endif	/* NISDB_MT_DEBUG */
389 			}
390 			(void) mutex_unlock(&rw->mutex);
391 			return (ret);
392 		}
393 		if (rr != 0) {
394 			rr->wait = 0;
395 			if (rw->reader_blocked > 0)
396 				rw->reader_blocked--;
397 #ifdef	NISDB_MT_DEBUG
398 			else
399 				abort();
400 #endif	/* NISDB_MT_DEBUG */
401 		}
402 	}
403 
404 	rr = increment_reader(myself, rw);
405 	ret = mutex_unlock(&rw->mutex);
406 	return ((rr == 0) ? ENOMEM : ret);
407 }
408 
409 
410 int
__nisdb_wulock(__nisdb_rwlock_t * rw)411 __nisdb_wulock(__nisdb_rwlock_t *rw) {
412 
413 	int		ret;
414 	pthread_t	myself = pthread_self();
415 
416 	if (rw == 0) {
417 #ifdef	NISDB_MT_DEBUG
418 		/* This shouldn't happen */
419 		abort();
420 #endif	/* NISDB_MT_DEBUG */
421 		return (EFAULT);
422 	}
423 
424 	if (rw->destroyed != 0)
425 		return (ESHUTDOWN);
426 
427 	if ((ret = mutex_lock(&rw->mutex)) != 0)
428 		return (ret);
429 
430 	if (rw->destroyed != 0) {
431 		(void) mutex_unlock(&rw->mutex);
432 		return (ESHUTDOWN);
433 	}
434 
435 	/* Sanity check */
436 	if (rw->writer_count == 0 ||
437 		rw->writer.id != myself || rw->writer.count == 0) {
438 #ifdef	NISDB_MT_DEBUG
439 		abort();
440 #endif	/* NISDB_MT_DEBUG */
441 		(void) mutex_unlock(&rw->mutex);
442 		return (ENOLCK);
443 	}
444 
445 	rw->writer.count--;
446 	if (rw->writer.count == 0) {
447 		rw->writer.id = INV_PTHREAD_ID;
448 		rw->writer_count = 0;
449 		if ((ret = cond_broadcast(&rw->cv)) != 0) {
450 			(void) mutex_unlock(&rw->mutex);
451 			return (ret);
452 		}
453 	}
454 
455 	return (mutex_unlock(&rw->mutex));
456 }
457 
458 
459 int
__nisdb_rulock(__nisdb_rwlock_t * rw)460 __nisdb_rulock(__nisdb_rwlock_t *rw) {
461 
462 	int		ret;
463 	pthread_t	myself = pthread_self();
464 	__nisdb_rl_t	*rr, *prev;
465 
466 	if (rw == 0) {
467 #ifdef	NISDB_MT_DEBUG
468 		abort();
469 #endif	/* NISDB_MT_DEBUG */
470 		return (EFAULT);
471 	}
472 
473 	if (rw->destroyed != 0)
474 		return (ESHUTDOWN);
475 
476 	if (rw->force_write)
477 		return (__nisdb_wulock(rw));
478 
479 	if ((ret = mutex_lock(&rw->mutex)) != 0)
480 		return (ret);
481 
482 	if (rw->destroyed != 0) {
483 		(void) mutex_unlock(&rw->mutex);
484 		return (ESHUTDOWN);
485 	}
486 
487 	/* Sanity check */
488 	if (rw->reader_count == 0 ||
489 		(rw->writer_count > 0 && rw->writer.id != myself)) {
490 #ifdef	NISDB_MT_DEBUG
491 		abort();
492 #endif	/* NISDB_MT_DEBUG */
493 		(void) mutex_unlock(&rw->mutex);
494 		return (ENOLCK);
495 	}
496 
497 	/* Find the reader record */
498 	for (rr = &rw->reader, prev = 0; rr != 0; prev = rr, rr = rr->next) {
499 		if (rr->id == myself)
500 			break;
501 	}
502 
503 	if (rr == 0 || rr->count == 0) {
504 #ifdef	NISDB_MT_DEBUG
505 		abort();
506 #endif	/* NISDB_MT_DEBUG */
507 		(void) mutex_unlock(&rw->mutex);
508 		return (ENOLCK);
509 	}
510 
511 	rr->count--;
512 	if (rr->count == 0) {
513 		if (rr != &rw->reader) {
514 			/* Remove item from list and free it */
515 			prev->next = rr->next;
516 			free(rr);
517 		} else {
518 			/*
519 			 * First record: copy second to first, and free second
520 			 * record.
521 			 */
522 			if (rr->next != 0) {
523 				rr = rr->next;
524 				rw->reader.id = rr->id;
525 				rw->reader.count = rr->count;
526 				rw->reader.next = rr->next;
527 				free(rr);
528 			} else {
529 				/* Decomission the first record */
530 				rr->id = INV_PTHREAD_ID;
531 			}
532 		}
533 		rw->reader_count--;
534 	}
535 
536 	/* If there are no readers, wake up any waiting writer */
537 	if (rw->reader_count == 0) {
538 		if ((ret = cond_broadcast(&rw->cv)) != 0) {
539 			(void) mutex_unlock(&rw->mutex);
540 			return (ret);
541 		}
542 	}
543 
544 	return (mutex_unlock(&rw->mutex));
545 }
546 
547 
548 /* Return zero if write lock held by this thread, non-zero otherwise */
549 int
__nisdb_assert_wheld(__nisdb_rwlock_t * rw)550 __nisdb_assert_wheld(__nisdb_rwlock_t *rw) {
551 
552 	int	ret;
553 
554 
555 	if (rw == 0) {
556 #ifdef	NISDB_MT_DEBUG
557 		abort();
558 #endif	/* NISDB_MT_DEBUG */
559 		return (EFAULT);
560 	}
561 
562 	if (rw->destroyed != 0)
563 		return (ESHUTDOWN);
564 
565 	if ((ret = mutex_lock(&rw->mutex)) != 0)
566 		return (ret);
567 
568 	if (rw->destroyed != 0) {
569 		(void) mutex_unlock(&rw->mutex);
570 		return (ESHUTDOWN);
571 	}
572 
573 	if (rw->writer_count == 0 || rw->writer.id != pthread_self()) {
574 		ret = mutex_unlock(&rw->mutex);
575 		return ((ret == 0) ? -1 : ret);
576 	}
577 
578 	/*
579 	 * We're holding the lock, so we should return zero. Since
580 	 * that's what mutex_unlock() does if it succeeds, we just
581 	 * return the value of mutex_unlock().
582 	 */
583 	return (mutex_unlock(&rw->mutex));
584 }
585 
586 
587 /* Return zero if read lock held by this thread, non-zero otherwise */
588 int
__nisdb_assert_rheld(__nisdb_rwlock_t * rw)589 __nisdb_assert_rheld(__nisdb_rwlock_t *rw) {
590 
591 	int		ret;
592 	pthread_t	myself = pthread_self();
593 	__nisdb_rl_t	*rr;
594 
595 
596 	if (rw == 0) {
597 #ifdef	NISDB_MT_DEBUG
598 		abort();
599 #endif	/* NISDB_MT_DEBUG */
600 		return (EFAULT);
601 	}
602 
603 	if (rw->destroyed != 0)
604 		return (ESHUTDOWN);
605 
606 	if (rw->force_write)
607 		return (__nisdb_assert_wheld(rw));
608 
609 	if ((ret = mutex_lock(&rw->mutex)) != 0)
610 		return (ret);
611 
612 	if (rw->destroyed != 0) {
613 		(void) mutex_unlock(&rw->mutex);
614 		return (ESHUTDOWN);
615 	}
616 
617 	/* Write lock also OK */
618 	if (rw->writer_count > 0 && rw->writer.id == myself) {
619 		(void) mutex_unlock(&rw->mutex);
620 		return (0);
621 	}
622 
623 	if (rw->reader_count == 0) {
624 		(void) mutex_unlock(&rw->mutex);
625 		return (EBUSY);
626 	}
627 
628 	rr = &rw->reader;
629 	do {
630 		if (rr->id == myself) {
631 			(void) mutex_unlock(&rw->mutex);
632 			return (0);
633 		}
634 		rr = rr->next;
635 	} while (rr != 0);
636 
637 	ret = mutex_unlock(&rw->mutex);
638 	return ((ret == 0) ? EBUSY : ret);
639 }
640 
641 
642 int
__nisdb_destroy_lock(__nisdb_rwlock_t * rw)643 __nisdb_destroy_lock(__nisdb_rwlock_t *rw) {
644 
645 	int		ret;
646 	pthread_t	myself = pthread_self();
647 
648 
649 	if (rw == 0) {
650 #ifdef	NISDB_MT_DEBUG
651 		abort();
652 #endif	/* NISDB_MT_DEBUG */
653 		return (EFAULT);
654 	}
655 
656 	if (rw->destroyed != 0)
657 		return (ESHUTDOWN);
658 
659 	if ((ret = mutex_lock(&rw->mutex)) != 0)
660 		return (ret);
661 
662 	if (rw->destroyed != 0) {
663 		(void) mutex_unlock(&rw->mutex);
664 		return (ESHUTDOWN);
665 	}
666 
667 	/*
668 	 * Only proceed if if there are neither readers nor writers
669 	 * other than this thread. Also, no nested locks may be in
670 	 * effect.
671 	 */
672 	if (((rw->writer_count > 0 &&
673 	    (rw->writer.id != myself || rw->writer.count != 1)) ||
674 	    (rw->reader_count > 0 &&
675 	    !(rw->reader_count == 1 && rw->reader.id == myself &&
676 	    rw->reader.count == 1))) ||
677 	    (rw->writer_count > 0 && rw->reader_count > 0)) {
678 #ifdef	NISDB_MT_DEBUG
679 		abort();
680 #endif	/* NISDB_MT_DEBUG */
681 		(void) mutex_unlock(&rw->mutex);
682 		return (ENOLCK);
683 	}
684 
685 	/*
686 	 * Mark lock destroyed, so that any thread waiting on the mutex
687 	 * will know what's what. Of course, this is a bit iffy, since
688 	 * we're probably being called from a destructor, and the structure
689 	 * where we live will soon cease to exist (i.e., be freed and
690 	 * perhaps re-used). Still, we can only do our best, and give
691 	 * those other threads the best chance possible.
692 	 */
693 	rw->destroyed++;
694 
695 	return (mutex_unlock(&rw->mutex));
696 }
697 
698 void
__nisdb_lock_report(__nisdb_rwlock_t * rw)699 __nisdb_lock_report(__nisdb_rwlock_t *rw) {
700 	char		*myself = "__nisdb_lock_report";
701 
702 	if (rw == 0) {
703 		printf("%s: NULL argument\n", myself);
704 		return;
705 	}
706 
707 	if (rw->destroyed)
708 		printf("0x%x: DESTROYED\n", rw);
709 
710 	printf("0x%x: Read locking %s\n",
711 		rw, rw->force_write ? "disallowed" : "allowed");
712 
713 	if (rw->writer_count == 0)
714 		printf("0x%x: No writer\n", rw);
715 	else if (rw->writer_count == 1) {
716 		printf("0x%x: Write locked by %d, depth = %d\n",
717 			rw, rw->writer.id, rw->writer.count);
718 		if (rw->writer.wait)
719 			printf("0x%x:\tWriter blocked\n", rw);
720 	} else
721 		printf("0x%x: Invalid writer count = %d\n",
722 			rw, rw->writer_count);
723 
724 	if (rw->reader_count == 0)
725 		printf("0x%x: No readers\n", rw);
726 	else {
727 		__nisdb_rl_t	*r;
728 
729 		printf("0x%x: %d readers, %d blocked\n",
730 			rw, rw->reader_count, rw->reader_blocked);
731 		for (r = &rw->reader; r != 0; r = r->next) {
732 			printf("0x%x:\tthread %d, depth = %d%s\n",
733 				rw, r->id, r->count,
734 				(r->wait ? " (blocked)" : ""));
735 		}
736 	}
737 }
738