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