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  * $DragonFly: src/lib/libthread_xu/thread/thr_rwlock.c,v 1.1 2005/02/01 12:38:27 davidxu Exp $
28  */
29 
30 #include <errno.h>
31 #include <limits.h>
32 #include <stdlib.h>
33 
34 #include <pthread.h>
35 #include "thr_private.h"
36 
37 /* maximum number of times a read lock may be obtained */
38 #define	MAX_READ_LOCKS		(INT_MAX - 1)
39 
40 __weak_reference(_pthread_rwlock_destroy, pthread_rwlock_destroy);
41 __weak_reference(_pthread_rwlock_init, pthread_rwlock_init);
42 __weak_reference(_pthread_rwlock_rdlock, pthread_rwlock_rdlock);
43 __weak_reference(_pthread_rwlock_timedrdlock, pthread_rwlock_timedrdlock);
44 __weak_reference(_pthread_rwlock_tryrdlock, pthread_rwlock_tryrdlock);
45 __weak_reference(_pthread_rwlock_trywrlock, pthread_rwlock_trywrlock);
46 __weak_reference(_pthread_rwlock_unlock, pthread_rwlock_unlock);
47 __weak_reference(_pthread_rwlock_wrlock, pthread_rwlock_wrlock);
48 __weak_reference(_pthread_rwlock_timedwrlock, pthread_rwlock_timedwrlock);
49 
50 /*
51  * Prototypes
52  */
53 
54 static int
55 rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
56 {
57 	pthread_rwlock_t prwlock;
58 	int ret;
59 
60 	/* allocate rwlock object */
61 	prwlock = (pthread_rwlock_t)malloc(sizeof(struct pthread_rwlock));
62 
63 	if (prwlock == NULL)
64 		return (ENOMEM);
65 
66 	/* initialize the lock */
67 	if ((ret = _pthread_mutex_init(&prwlock->lock, NULL)) != 0)
68 		free(prwlock);
69 	else {
70 		/* initialize the read condition signal */
71 		ret = _pthread_cond_init(&prwlock->read_signal, NULL);
72 
73 		if (ret != 0) {
74 			_pthread_mutex_destroy(&prwlock->lock);
75 			free(prwlock);
76 		} else {
77 			/* initialize the write condition signal */
78 			ret = _pthread_cond_init(&prwlock->write_signal, NULL);
79 
80 			if (ret != 0) {
81 				_pthread_cond_destroy(&prwlock->read_signal);
82 				_pthread_mutex_destroy(&prwlock->lock);
83 				free(prwlock);
84 			} else {
85 				/* success */
86 				prwlock->state = 0;
87 				prwlock->blocked_writers = 0;
88 				*rwlock = prwlock;
89 			}
90 		}
91 	}
92 
93 	return (ret);
94 }
95 
96 int
97 _pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
98 {
99 	int ret;
100 
101 	if (rwlock == NULL)
102 		ret = EINVAL;
103 	else {
104 		pthread_rwlock_t prwlock;
105 
106 		prwlock = *rwlock;
107 
108 		_pthread_mutex_destroy(&prwlock->lock);
109 		_pthread_cond_destroy(&prwlock->read_signal);
110 		_pthread_cond_destroy(&prwlock->write_signal);
111 		free(prwlock);
112 
113 		*rwlock = NULL;
114 
115 		ret = 0;
116 	}
117 	return (ret);
118 }
119 
120 static int
121 init_static(struct pthread *thread, pthread_rwlock_t *rwlock)
122 {
123 	int ret;
124 
125 	THR_LOCK_ACQUIRE(thread, &_rwlock_static_lock);
126 
127 	if (*rwlock == NULL)
128 		ret = rwlock_init(rwlock, NULL);
129 	else
130 		ret = 0;
131 
132 	THR_LOCK_RELEASE(thread, &_rwlock_static_lock);
133 
134 	return (ret);
135 }
136 
137 int
138 _pthread_rwlock_init (pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
139 {
140 	*rwlock = NULL;
141 	return (rwlock_init(rwlock, attr));
142 }
143 
144 static int
145 rwlock_rdlock_common (pthread_rwlock_t *rwlock, const struct timespec *abstime)
146 {
147 	struct pthread *curthread = _get_curthread();
148 	pthread_rwlock_t prwlock;
149 	int ret;
150 
151 	if (rwlock == NULL)
152 		return (EINVAL);
153 
154 	prwlock = *rwlock;
155 
156 	/* check for static initialization */
157 	if (prwlock == NULL) {
158 		if ((ret = init_static(curthread, rwlock)) != 0)
159 			return (ret);
160 
161 		prwlock = *rwlock;
162 	}
163 
164 	/* grab the monitor lock */
165 	if ((ret = _thr_mutex_lock(&prwlock->lock)) != 0)
166 		return (ret);
167 
168 	/* check lock count */
169 	if (prwlock->state == MAX_READ_LOCKS) {
170 		_thr_mutex_unlock(&prwlock->lock);
171 		return (EAGAIN);
172 	}
173 
174 	curthread = _get_curthread();
175 	if ((curthread->rdlock_count > 0) && (prwlock->state > 0)) {
176 		/*
177 		 * To avoid having to track all the rdlocks held by
178 		 * a thread or all of the threads that hold a rdlock,
179 		 * we keep a simple count of all the rdlocks held by
180 		 * a thread.  If a thread holds any rdlocks it is
181 		 * possible that it is attempting to take a recursive
182 		 * rdlock.  If there are blocked writers and precedence
183 		 * is given to them, then that would result in the thread
184 		 * deadlocking.  So allowing a thread to take the rdlock
185 		 * when it already has one or more rdlocks avoids the
186 		 * deadlock.  I hope the reader can follow that logic ;-)
187 		 */
188 		;	/* nothing needed */
189 	} else {
190 		/* give writers priority over readers */
191 		while (prwlock->blocked_writers || prwlock->state < 0) {
192 			if (abstime)
193 				ret = _pthread_cond_timedwait
194 				    (&prwlock->read_signal,
195 				    &prwlock->lock, abstime);
196 			else
197 				ret = _thr_cond_wait(&prwlock->read_signal,
198 			    &prwlock->lock);
199 			if (ret != 0) {
200 				/* can't do a whole lot if this fails */
201 				_thr_mutex_unlock(&prwlock->lock);
202 				return (ret);
203 			}
204 		}
205 	}
206 
207 	curthread->rdlock_count++;
208 	prwlock->state++; /* indicate we are locked for reading */
209 
210 	/*
211 	 * Something is really wrong if this call fails.  Returning
212 	 * error won't do because we've already obtained the read
213 	 * lock.  Decrementing 'state' is no good because we probably
214 	 * don't have the monitor lock.
215 	 */
216 	_thr_mutex_unlock(&prwlock->lock);
217 
218 	return (ret);
219 }
220 
221 int
222 _pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
223 {
224 	return (rwlock_rdlock_common(rwlock, NULL));
225 }
226 
227 __strong_reference(_pthread_rwlock_rdlock, _thr_rwlock_rdlock);
228 
229 int
230 _pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
231 	 const struct timespec *abstime)
232 {
233 	return (rwlock_rdlock_common(rwlock, abstime));
234 }
235 
236 int
237 _pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
238 {
239 	struct pthread *curthread = _get_curthread();
240 	pthread_rwlock_t prwlock;
241 	int ret;
242 
243 	if (rwlock == NULL)
244 		return (EINVAL);
245 
246 	prwlock = *rwlock;
247 
248 	/* check for static initialization */
249 	if (prwlock == NULL) {
250 		if ((ret = init_static(curthread, rwlock)) != 0)
251 			return (ret);
252 
253 		prwlock = *rwlock;
254 	}
255 
256 	/* grab the monitor lock */
257 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
258 		return (ret);
259 
260 	curthread = _get_curthread();
261 	if (prwlock->state == MAX_READ_LOCKS)
262 		ret = EAGAIN;
263 	else if ((curthread->rdlock_count > 0) && (prwlock->state > 0)) {
264 		/* see comment for pthread_rwlock_rdlock() */
265 		curthread->rdlock_count++;
266 		prwlock->state++;
267 	}
268 	/* give writers priority over readers */
269 	else if (prwlock->blocked_writers || prwlock->state < 0)
270 		ret = EBUSY;
271 	else {
272 		curthread->rdlock_count++;
273 		prwlock->state++; /* indicate we are locked for reading */
274 	}
275 
276 	/* see the comment on this in pthread_rwlock_rdlock */
277 	_pthread_mutex_unlock(&prwlock->lock);
278 
279 	return (ret);
280 }
281 
282 int
283 _pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock)
284 {
285 	struct pthread *curthread = _get_curthread();
286 	pthread_rwlock_t prwlock;
287 	int ret;
288 
289 	if (rwlock == NULL)
290 		return (EINVAL);
291 
292 	prwlock = *rwlock;
293 
294 	/* check for static initialization */
295 	if (prwlock == NULL) {
296 		if ((ret = init_static(curthread, rwlock)) != 0)
297 			return (ret);
298 
299 		prwlock = *rwlock;
300 	}
301 
302 	/* grab the monitor lock */
303 	if ((ret = _pthread_mutex_lock(&prwlock->lock)) != 0)
304 		return (ret);
305 
306 	if (prwlock->state != 0)
307 		ret = EBUSY;
308 	else
309 		/* indicate we are locked for writing */
310 		prwlock->state = -1;
311 
312 	/* see the comment on this in pthread_rwlock_rdlock */
313 	_pthread_mutex_unlock(&prwlock->lock);
314 
315 	return (ret);
316 }
317 
318 int
319 _pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
320 {
321 	struct pthread *curthread;
322 	pthread_rwlock_t prwlock;
323 	int ret;
324 
325 	if (rwlock == NULL)
326 		return (EINVAL);
327 
328 	prwlock = *rwlock;
329 
330 	if (prwlock == NULL)
331 		return (EINVAL);
332 
333 	/* grab the monitor lock */
334 	if ((ret = _thr_mutex_lock(&prwlock->lock)) != 0)
335 		return (ret);
336 
337 	curthread = _get_curthread();
338 	if (prwlock->state > 0) {
339 		curthread->rdlock_count--;
340 		prwlock->state--;
341 		if (prwlock->state == 0 && prwlock->blocked_writers)
342 			ret = _thr_cond_signal(&prwlock->write_signal);
343 	} else if (prwlock->state < 0) {
344 		prwlock->state = 0;
345 
346 		if (prwlock->blocked_writers)
347 			ret = _thr_cond_signal(&prwlock->write_signal);
348 		else
349 			ret = _thr_cond_broadcast(&prwlock->read_signal);
350 	} else
351 		ret = EINVAL;
352 
353 	/* see the comment on this in pthread_rwlock_rdlock */
354 	_thr_mutex_unlock(&prwlock->lock);
355 
356 	return (ret);
357 }
358 
359 __strong_reference(_pthread_rwlock_unlock, _thr_rwlock_unlock);
360 
361 static int
362 rwlock_wrlock_common (pthread_rwlock_t *rwlock, const struct timespec *abstime)
363 {
364 	struct pthread *curthread = _get_curthread();
365 	pthread_rwlock_t prwlock;
366 	int ret;
367 
368 	if (rwlock == NULL)
369 		return (EINVAL);
370 
371 	prwlock = *rwlock;
372 
373 	/* check for static initialization */
374 	if (prwlock == NULL) {
375 		if ((ret = init_static(curthread, rwlock)) != 0)
376 			return (ret);
377 
378 		prwlock = *rwlock;
379 	}
380 
381 	/* grab the monitor lock */
382 	if ((ret = _thr_mutex_lock(&prwlock->lock)) != 0)
383 		return (ret);
384 
385 	while (prwlock->state != 0) {
386 		prwlock->blocked_writers++;
387 
388 		if (abstime != NULL)
389 			ret = _pthread_cond_timedwait(&prwlock->write_signal,
390 			    &prwlock->lock, abstime);
391 		else
392 			ret = _thr_cond_wait(&prwlock->write_signal,
393 			    &prwlock->lock);
394 		if (ret != 0) {
395 			prwlock->blocked_writers--;
396 			_thr_mutex_unlock(&prwlock->lock);
397 			return (ret);
398 		}
399 
400 		prwlock->blocked_writers--;
401 	}
402 
403 	/* indicate we are locked for writing */
404 	prwlock->state = -1;
405 
406 	/* see the comment on this in pthread_rwlock_rdlock */
407 	_thr_mutex_unlock(&prwlock->lock);
408 
409 	return (ret);
410 }
411 
412 int
413 _pthread_rwlock_wrlock (pthread_rwlock_t *rwlock)
414 {
415 	return (rwlock_wrlock_common (rwlock, NULL));
416 }
417 __strong_reference(_pthread_rwlock_wrlock, _thr_rwlock_wrlock);
418 
419 int
420 _pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
421     const struct timespec *abstime)
422 {
423 	return (rwlock_wrlock_common (rwlock, abstime));
424 }
425