1 /*-
2  * Copyright (c) 1998 Alex Nash
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD: src/lib/libpthread/thread/thr_rwlock.c,v 1.14 2004/01/08 15:37:09 deischen Exp $
27  */
28 
29 #include "namespace.h"
30 #include <machine/tls.h>
31 #include <errno.h>
32 #include <limits.h>
33 #include <stdlib.h>
34 #include <pthread.h>
35 #include "un-namespace.h"
36 #include "thr_private.h"
37 
38 /* maximum number of times a read lock may be obtained */
39 #define	MAX_READ_LOCKS		(INT_MAX - 1)
40 
41 umtx_t	_rwlock_static_lock;
42 
43 static int
44 rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr __unused)
45 {
46 	pthread_rwlock_t prwlock;
47 	int ret;
48 
49 	/* allocate rwlock object */
50 	prwlock = (pthread_rwlock_t)malloc(sizeof(struct pthread_rwlock));
51 
52 	if (prwlock == NULL)
53 		return (ENOMEM);
54 
55 	/* initialize the lock */
56 	if ((ret = _pthread_mutex_init(&prwlock->lock, NULL)) != 0)
57 		free(prwlock);
58 	else {
59 		/* initialize the read condition signal */
60 		ret = _pthread_cond_init(&prwlock->read_signal, NULL);
61 
62 		if (ret != 0) {
63 			_pthread_mutex_destroy(&prwlock->lock);
64 			free(prwlock);
65 		} else {
66 			/* initialize the write condition signal */
67 			ret = _pthread_cond_init(&prwlock->write_signal, NULL);
68 
69 			if (ret != 0) {
70 				_pthread_cond_destroy(&prwlock->read_signal);
71 				_pthread_mutex_destroy(&prwlock->lock);
72 				free(prwlock);
73 			} else {
74 				/* success */
75 				prwlock->state = 0;
76 				prwlock->blocked_writers = 0;
77 				*rwlock = prwlock;
78 			}
79 		}
80 	}
81 
82 	return (ret);
83 }
84 
85 int
86 _pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
87 {
88 	int ret;
89 
90 	if (rwlock == NULL)
91 		ret = EINVAL;
92 	else if (*rwlock == NULL)
93 		ret = 0;
94 	else {
95 		pthread_rwlock_t prwlock;
96 
97 		prwlock = *rwlock;
98 
99 		_pthread_mutex_destroy(&prwlock->lock);
100 		_pthread_cond_destroy(&prwlock->read_signal);
101 		_pthread_cond_destroy(&prwlock->write_signal);
102 		free(prwlock);
103 
104 		*rwlock = NULL;
105 
106 		ret = 0;
107 	}
108 	return (ret);
109 }
110 
111 static int
112 init_static(struct pthread *thread, pthread_rwlock_t *rwlock)
113 {
114 	int ret;
115 
116 	THR_LOCK_ACQUIRE(thread, &_rwlock_static_lock);
117 
118 	if (*rwlock == NULL)
119 		ret = rwlock_init(rwlock, NULL);
120 	else
121 		ret = 0;
122 
123 	THR_LOCK_RELEASE(thread, &_rwlock_static_lock);
124 
125 	return (ret);
126 }
127 
128 int
129 _pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
130 {
131 	*rwlock = NULL;
132 	return (rwlock_init(rwlock, attr));
133 }
134 
135 static int
136 rwlock_rdlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime)
137 {
138 	struct pthread *curthread = tls_get_curthread();
139 	pthread_rwlock_t prwlock;
140 	int ret;
141 
142 	if (rwlock == NULL)
143 		return (EINVAL);
144 
145 	prwlock = *rwlock;
146 
147 	/* check for static initialization */
148 	if (prwlock == NULL) {
149 		if ((ret = init_static(curthread, rwlock)) != 0)
150 			return (ret);
151 
152 		prwlock = *rwlock;
153 	}
154 
155 	/* grab the monitor lock */
156 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
157 		return (ret);
158 
159 	/* check lock count */
160 	if (prwlock->state == MAX_READ_LOCKS) {
161 		_pthread_mutex_unlock(&prwlock->lock);
162 		return (EAGAIN);
163 	}
164 
165 	curthread = tls_get_curthread();
166 	if ((curthread->rdlock_count > 0) && (prwlock->state > 0)) {
167 		/*
168 		 * To avoid having to track all the rdlocks held by
169 		 * a thread or all of the threads that hold a rdlock,
170 		 * we keep a simple count of all the rdlocks held by
171 		 * a thread.  If a thread holds any rdlocks it is
172 		 * possible that it is attempting to take a recursive
173 		 * rdlock.  If there are blocked writers and precedence
174 		 * is given to them, then that would result in the thread
175 		 * deadlocking.  So allowing a thread to take the rdlock
176 		 * when it already has one or more rdlocks avoids the
177 		 * deadlock.  I hope the reader can follow that logic ;-)
178 		 */
179 		;	/* nothing needed */
180 	} else {
181 		/* give writers priority over readers */
182 		while (prwlock->blocked_writers || prwlock->state < 0) {
183 			if (abstime)
184 				ret = _pthread_cond_timedwait
185 				    (&prwlock->read_signal,
186 				    &prwlock->lock, abstime);
187 			else
188 				ret = _pthread_cond_wait(&prwlock->read_signal,
189 			    &prwlock->lock);
190 			if (ret != 0) {
191 				/* can't do a whole lot if this fails */
192 				_pthread_mutex_unlock(&prwlock->lock);
193 				return (ret);
194 			}
195 		}
196 	}
197 
198 	curthread->rdlock_count++;
199 	prwlock->state++; /* indicate we are locked for reading */
200 
201 	/*
202 	 * Something is really wrong if this call fails.  Returning
203 	 * error won't do because we've already obtained the read
204 	 * lock.  Decrementing 'state' is no good because we probably
205 	 * don't have the monitor lock.
206 	 */
207 	_pthread_mutex_unlock(&prwlock->lock);
208 
209 	return (ret);
210 }
211 
212 int
213 _pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
214 {
215 	return (rwlock_rdlock_common(rwlock, NULL));
216 }
217 
218 int
219 _pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
220 	 const struct timespec *abstime)
221 {
222 	return (rwlock_rdlock_common(rwlock, abstime));
223 }
224 
225 int
226 _pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
227 {
228 	struct pthread *curthread = tls_get_curthread();
229 	pthread_rwlock_t prwlock;
230 	int ret;
231 
232 	if (rwlock == NULL)
233 		return (EINVAL);
234 
235 	prwlock = *rwlock;
236 
237 	/* check for static initialization */
238 	if (prwlock == NULL) {
239 		if ((ret = init_static(curthread, rwlock)) != 0)
240 			return (ret);
241 
242 		prwlock = *rwlock;
243 	}
244 
245 	/* grab the monitor lock */
246 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
247 		return (ret);
248 
249 	curthread = tls_get_curthread();
250 	if (prwlock->state == MAX_READ_LOCKS)
251 		ret = EAGAIN;
252 	else if ((curthread->rdlock_count > 0) && (prwlock->state > 0)) {
253 		/* see comment for pthread_rwlock_rdlock() */
254 		curthread->rdlock_count++;
255 		prwlock->state++;
256 	}
257 	/* give writers priority over readers */
258 	else if (prwlock->blocked_writers || prwlock->state < 0)
259 		ret = EBUSY;
260 	else {
261 		curthread->rdlock_count++;
262 		prwlock->state++; /* indicate we are locked for reading */
263 	}
264 
265 	/* see the comment on this in pthread_rwlock_rdlock */
266 	_pthread_mutex_unlock(&prwlock->lock);
267 
268 	return (ret);
269 }
270 
271 int
272 _pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock)
273 {
274 	struct pthread *curthread = tls_get_curthread();
275 	pthread_rwlock_t prwlock;
276 	int ret;
277 
278 	if (rwlock == NULL)
279 		return (EINVAL);
280 
281 	prwlock = *rwlock;
282 
283 	/* check for static initialization */
284 	if (prwlock == NULL) {
285 		if ((ret = init_static(curthread, rwlock)) != 0)
286 			return (ret);
287 
288 		prwlock = *rwlock;
289 	}
290 
291 	/* grab the monitor lock */
292 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
293 		return (ret);
294 
295 	if (prwlock->state != 0)
296 		ret = EBUSY;
297 	else
298 		/* indicate we are locked for writing */
299 		prwlock->state = -1;
300 
301 	/* see the comment on this in pthread_rwlock_rdlock */
302 	_pthread_mutex_unlock(&prwlock->lock);
303 
304 	return (ret);
305 }
306 
307 int
308 _pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
309 {
310 	struct pthread *curthread;
311 	pthread_rwlock_t prwlock;
312 	int ret;
313 
314 	if (rwlock == NULL)
315 		return (EINVAL);
316 
317 	prwlock = *rwlock;
318 
319 	if (prwlock == NULL)
320 		return (EINVAL);
321 
322 	/* grab the monitor lock */
323 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
324 		return (ret);
325 
326 	curthread = tls_get_curthread();
327 	if (prwlock->state > 0) {
328 		curthread->rdlock_count--;
329 		prwlock->state--;
330 		if (prwlock->state == 0 && prwlock->blocked_writers)
331 			ret = _pthread_cond_signal(&prwlock->write_signal);
332 	} else if (prwlock->state < 0) {
333 		prwlock->state = 0;
334 
335 		if (prwlock->blocked_writers)
336 			ret = _pthread_cond_signal(&prwlock->write_signal);
337 		else
338 			ret = _pthread_cond_broadcast(&prwlock->read_signal);
339 	} else
340 		ret = EINVAL;
341 
342 	/* see the comment on this in pthread_rwlock_rdlock */
343 	_pthread_mutex_unlock(&prwlock->lock);
344 
345 	return (ret);
346 }
347 
348 static int
349 rwlock_wrlock_common (pthread_rwlock_t *rwlock, const struct timespec *abstime)
350 {
351 	struct pthread *curthread = tls_get_curthread();
352 	pthread_rwlock_t prwlock;
353 	int ret;
354 
355 	if (rwlock == NULL)
356 		return (EINVAL);
357 
358 	prwlock = *rwlock;
359 
360 	/* check for static initialization */
361 	if (prwlock == NULL) {
362 		if ((ret = init_static(curthread, rwlock)) != 0)
363 			return (ret);
364 
365 		prwlock = *rwlock;
366 	}
367 
368 	/* grab the monitor lock */
369 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
370 		return (ret);
371 
372 	while (prwlock->state != 0) {
373 		prwlock->blocked_writers++;
374 
375 		if (abstime != NULL)
376 			ret = _pthread_cond_timedwait(&prwlock->write_signal,
377 			    &prwlock->lock, abstime);
378 		else
379 			ret = _pthread_cond_wait(&prwlock->write_signal,
380 			    &prwlock->lock);
381 		if (ret != 0) {
382 			prwlock->blocked_writers--;
383 			_pthread_mutex_unlock(&prwlock->lock);
384 			return (ret);
385 		}
386 
387 		prwlock->blocked_writers--;
388 	}
389 
390 	/* indicate we are locked for writing */
391 	prwlock->state = -1;
392 
393 	/* see the comment on this in pthread_rwlock_rdlock */
394 	_pthread_mutex_unlock(&prwlock->lock);
395 
396 	return (ret);
397 }
398 
399 int
400 _pthread_rwlock_wrlock (pthread_rwlock_t *rwlock)
401 {
402 	return (rwlock_wrlock_common (rwlock, NULL));
403 }
404 
405 int
406 _pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
407     const struct timespec *abstime)
408 {
409 	return (rwlock_wrlock_common (rwlock, abstime));
410 }
411 
412 __strong_reference(_pthread_rwlock_destroy, pthread_rwlock_destroy);
413 __strong_reference(_pthread_rwlock_init, pthread_rwlock_init);
414 __strong_reference(_pthread_rwlock_rdlock, pthread_rwlock_rdlock);
415 __strong_reference(_pthread_rwlock_timedrdlock, pthread_rwlock_timedrdlock);
416 __strong_reference(_pthread_rwlock_tryrdlock, pthread_rwlock_tryrdlock);
417 __strong_reference(_pthread_rwlock_trywrlock, pthread_rwlock_trywrlock);
418 __strong_reference(_pthread_rwlock_unlock, pthread_rwlock_unlock);
419 __strong_reference(_pthread_rwlock_wrlock, pthread_rwlock_wrlock);
420 __strong_reference(_pthread_rwlock_timedwrlock, pthread_rwlock_timedwrlock);
421