xref: /freebsd/lib/libthr/thread/thr_rwlock.c (revision 2172a5cbc5f6999263fbcf5a367032154db963b9)
1 /*-
2  * Copyright (c) 1998 Alex Nash
3  * Copyright (c) 2004 Michael Telahun Makonnen
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  * $FreeBSD$
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 /*
41  * For distinguishing operations on read and write locks.
42  */
43 enum rwlock_type {RWT_READ, RWT_WRITE};
44 
45 __weak_reference(_pthread_rwlock_destroy, pthread_rwlock_destroy);
46 __weak_reference(_pthread_rwlock_init, pthread_rwlock_init);
47 __weak_reference(_pthread_rwlock_rdlock, pthread_rwlock_rdlock);
48 __weak_reference(_pthread_rwlock_timedrdlock, pthread_rwlock_timedrdlock);
49 __weak_reference(_pthread_rwlock_timedwrlock, pthread_rwlock_timedwrlock);
50 __weak_reference(_pthread_rwlock_tryrdlock, pthread_rwlock_tryrdlock);
51 __weak_reference(_pthread_rwlock_trywrlock, pthread_rwlock_trywrlock);
52 __weak_reference(_pthread_rwlock_unlock, pthread_rwlock_unlock);
53 __weak_reference(_pthread_rwlock_wrlock, pthread_rwlock_wrlock);
54 
55 static int	insert_rwlock(struct pthread_rwlock *, enum rwlock_type);
56 static int	rwlock_rdlock_common(pthread_rwlock_t *, int,
57 		    const struct timespec *);
58 static int	rwlock_wrlock_common(pthread_rwlock_t *, int,
59 		    const struct timespec *);
60 
61 int
62 _pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
63 {
64 	pthread_rwlock_t prwlock;
65 
66 	if (rwlock == NULL || *rwlock == NULL)
67 		return (EINVAL);
68 
69 	prwlock = *rwlock;
70 
71 	if (prwlock->state != 0)
72 		return (EBUSY);
73 
74 	pthread_mutex_destroy(&prwlock->lock);
75 	pthread_cond_destroy(&prwlock->read_signal);
76 	pthread_cond_destroy(&prwlock->write_signal);
77 	free(prwlock);
78 
79 	*rwlock = NULL;
80 
81 	return (0);
82 }
83 
84 int
85 _pthread_rwlock_init (pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
86 {
87 	pthread_rwlock_t	prwlock;
88 	int			ret;
89 
90 	/* allocate rwlock object */
91 	prwlock = (pthread_rwlock_t)malloc(sizeof(struct pthread_rwlock));
92 
93 	if (prwlock == NULL) {
94 		ret = ENOMEM;
95 		goto out;
96 	}
97 
98 	/* initialize the lock */
99 	if ((ret = pthread_mutex_init(&prwlock->lock, NULL)) != 0)
100 		goto out;
101 
102 	/* initialize the read condition signal */
103 	if ((ret = pthread_cond_init(&prwlock->read_signal, NULL)) != 0)
104 		goto out_readcond;
105 
106 	/* initialize the write condition signal */
107 	if ((ret = pthread_cond_init(&prwlock->write_signal, NULL)) != 0)
108 		goto out_writecond;
109 
110 	/* success */
111 	prwlock->state		 = 0;
112 	prwlock->blocked_writers = 0;
113 
114 	*rwlock = prwlock;
115 	return (0);
116 
117 out_writecond:
118 	pthread_cond_destroy(&prwlock->read_signal);
119 out_readcond:
120 	pthread_mutex_destroy(&prwlock->lock);
121 out:
122 	if (prwlock != NULL)
123 		free(prwlock);
124 	return(ret);
125 }
126 
127 /*
128  * If nonblocking is 0 this function will wait on the lock. If
129  * it is greater than 0 it will return immediately with EBUSY.
130  */
131 static int
132 rwlock_rdlock_common(pthread_rwlock_t *rwlock, int nonblocking,
133     const struct timespec *timeout)
134 {
135 	struct rwlock_held	*rh;
136 	pthread_rwlock_t 	prwlock;
137 	int			ret;
138 
139 	rh = NULL;
140 	if (rwlock == NULL || *rwlock == NULL)
141 		return(EINVAL);
142 
143 	/*
144 	 * Check for validity of the timeout parameter.
145 	 */
146 	if (timeout != NULL &&
147 	    (timeout->tv_nsec < 0 || timeout->tv_nsec >= 1000000000))
148 		return (EINVAL);
149 
150 	prwlock = *rwlock;
151 
152 	/* grab the monitor lock */
153 	if ((ret = pthread_mutex_lock(&prwlock->lock)) != 0)
154 		return(ret);
155 
156 	/* check lock count */
157 	if (prwlock->state == MAX_READ_LOCKS) {
158 		pthread_mutex_unlock(&prwlock->lock);
159 		return (EAGAIN);
160 	}
161 
162 	/* give writers priority over readers */
163 	while (prwlock->blocked_writers || prwlock->state < 0) {
164 		if (nonblocking) {
165 			pthread_mutex_unlock(&prwlock->lock);
166 			return (EBUSY);
167 		}
168 
169 		/*
170 		 * If this lock is already held for writing we have
171 		 * a deadlock situation.
172 		 */
173 		if (curthread->rwlockList != NULL && prwlock->state < 0) {
174 			LIST_FOREACH(rh, curthread->rwlockList, rh_link) {
175 				if (rh->rh_rwlock == prwlock &&
176 				    rh->rh_wrcount > 0) {
177 					pthread_mutex_unlock(&prwlock->lock);
178 					return (EDEADLK);
179 				}
180 			}
181 		}
182 		if (timeout == NULL)
183 			ret = pthread_cond_wait(&prwlock->read_signal,
184 			    &prwlock->lock);
185 		else
186 			ret = pthread_cond_timedwait(&prwlock->read_signal,
187 			    &prwlock->lock, timeout);
188 
189 		if (ret != 0 && ret != EINTR) {
190 			/* can't do a whole lot if this fails */
191 			pthread_mutex_unlock(&prwlock->lock);
192 			return(ret);
193 		}
194 	}
195 
196 	++prwlock->state; /* indicate we are locked for reading */
197 	ret = insert_rwlock(prwlock, RWT_READ);
198 	if (ret != 0) {
199 		pthread_mutex_unlock(&prwlock->lock);
200 		return (ret);
201 	}
202 
203 	/*
204 	 * Something is really wrong if this call fails.  Returning
205 	 * error won't do because we've already obtained the read
206 	 * lock.  Decrementing 'state' is no good because we probably
207 	 * don't have the monitor lock.
208 	 */
209 	pthread_mutex_unlock(&prwlock->lock);
210 
211 	return(0);
212 }
213 
214 int
215 _pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
216 {
217 	return (rwlock_rdlock_common(rwlock, 0, NULL));
218 }
219 
220 int
221 _pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock,
222     const struct timespec *timeout)
223 {
224 	return (rwlock_rdlock_common(rwlock, 0, timeout));
225 }
226 
227 int
228 _pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
229 {
230 	return (rwlock_rdlock_common(rwlock, 1, NULL));
231 }
232 
233 int
234 _pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
235 {
236 	struct rwlock_held	*rh;
237 	pthread_rwlock_t 	prwlock;
238 	int			ret;
239 
240 	rh = NULL;
241 	if (rwlock == NULL || *rwlock == NULL)
242 		return(EINVAL);
243 
244 	prwlock = *rwlock;
245 
246 	/* grab the monitor lock */
247 	if ((ret = pthread_mutex_lock(&prwlock->lock)) != 0)
248 		return(ret);
249 
250 	if (curthread->rwlockList != NULL) {
251 		LIST_FOREACH(rh, curthread->rwlockList, rh_link) {
252 			if (rh->rh_rwlock == prwlock)
253 				break;
254 		}
255 	}
256 	if (rh == NULL) {
257 		ret = EPERM;
258 		goto out;
259 	}
260 	if (prwlock->state > 0) {
261 		rh->rh_rdcount--;
262 		if (rh->rh_rdcount == 0) {
263 			LIST_REMOVE(rh, rh_link);
264 			free(rh);
265 		}
266 		if (--prwlock->state == 0 && prwlock->blocked_writers)
267 			ret = pthread_cond_signal(&prwlock->write_signal);
268 	} else if (prwlock->state < 0) {
269 		rh->rh_wrcount--;
270 		if (rh->rh_wrcount == 0) {
271 			LIST_REMOVE(rh, rh_link);
272 			free(rh);
273 		}
274 		prwlock->state = 0;
275 		if (prwlock->blocked_writers)
276 			ret = pthread_cond_signal(&prwlock->write_signal);
277 		else
278 			ret = pthread_cond_broadcast(&prwlock->read_signal);
279 	} else {
280 		/*
281 		 * No thread holds this lock. We should never get here.
282 		 */
283 		PTHREAD_ASSERT(0, "state=0 on read-write lock held by thread");
284 		ret = EPERM;
285 		goto out;
286 	}
287 
288 out:
289 	/* see the comment on this in rwlock_rdlock_common */
290 	pthread_mutex_unlock(&prwlock->lock);
291 
292 	return(ret);
293 }
294 
295 int
296 _pthread_rwlock_wrlock (pthread_rwlock_t *rwlock)
297 {
298 	return (rwlock_wrlock_common(rwlock, 0, NULL));
299 }
300 
301 int
302 _pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
303     const struct timespec *timeout)
304 {
305 	return (rwlock_wrlock_common(rwlock, 0, timeout));
306 }
307 
308 int
309 _pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock)
310 {
311 	return (rwlock_wrlock_common(rwlock, 1, NULL));
312 }
313 
314 /*
315  * If nonblocking is 0 this function will wait on the lock. If
316  * it is greater than 0 it will return immediately with EBUSY.
317  */
318 static int
319 rwlock_wrlock_common(pthread_rwlock_t *rwlock, int nonblocking,
320     const struct timespec *timeout)
321 {
322 	struct rwlock_held	*rh;
323 	pthread_rwlock_t 	prwlock;
324 	int			ret;
325 
326 	rh = NULL;
327 	if (rwlock == NULL || *rwlock == NULL)
328 		return(EINVAL);
329 
330 	/*
331 	 * Check the timeout value for validity.
332 	 */
333 	if (timeout != NULL &&
334 	    (timeout->tv_nsec < 0 || timeout->tv_nsec >= 1000000000))
335 		return (EINVAL);
336 
337 	prwlock = *rwlock;
338 
339 	/* grab the monitor lock */
340 	if ((ret = pthread_mutex_lock(&prwlock->lock)) != 0)
341 		return(ret);
342 
343 	while (prwlock->state != 0) {
344 		if (nonblocking) {
345 			pthread_mutex_unlock(&prwlock->lock);
346 			return (EBUSY);
347 		}
348 
349 		/*
350 		 * If this thread already holds the lock for reading
351 		 * or writing we have a deadlock situation.
352 		 */
353 		if (curthread->rwlockList != NULL) {
354 			LIST_FOREACH(rh, curthread->rwlockList, rh_link) {
355 				if (rh->rh_rwlock == prwlock &&
356 				    (rh->rh_rdcount > 0 || rh->rh_wrcount > 0)) {
357 					pthread_mutex_unlock(&prwlock->lock);
358 					return (EDEADLK);
359 					break;
360 				}
361 			}
362 		}
363 
364 		++prwlock->blocked_writers;
365 
366 		if (timeout == NULL)
367 			ret = pthread_cond_wait(&prwlock->write_signal,
368 			    &prwlock->lock);
369 		else
370 			ret = pthread_cond_timedwait(&prwlock->write_signal,
371 			    &prwlock->lock, timeout);
372 
373 		if (ret != 0 && ret != EINTR) {
374 			--prwlock->blocked_writers;
375 			pthread_mutex_unlock(&prwlock->lock);
376 			return(ret);
377 		}
378 
379 		--prwlock->blocked_writers;
380 	}
381 
382 	/* indicate we are locked for writing */
383 	prwlock->state = -1;
384 	ret = insert_rwlock(prwlock, RWT_WRITE);
385 	if (ret != 0) {
386 		pthread_mutex_unlock(&prwlock->lock);
387 		return (ret);
388 	}
389 
390 	/* see the comment on this in pthread_rwlock_rdlock */
391 	pthread_mutex_unlock(&prwlock->lock);
392 
393 	return(0);
394 }
395 
396 static int
397 insert_rwlock(struct pthread_rwlock *prwlock, enum rwlock_type rwt)
398 {
399 	struct rwlock_held *rh;
400 
401 	/*
402 	 * Initialize the rwlock list in the thread. Although this function
403 	 * may be called for many read-write locks, the initialization
404 	 * of the the head happens only once during the lifetime of
405 	 * the thread.
406 	 */
407 	if (curthread->rwlockList == NULL) {
408 		curthread->rwlockList =
409 		    (struct rwlock_listhead *)malloc(sizeof(struct rwlock_listhead));
410 		if (curthread->rwlockList == NULL) {
411 			return (ENOMEM);
412 		}
413 		LIST_INIT(curthread->rwlockList);
414 	}
415 
416 	LIST_FOREACH(rh, curthread->rwlockList, rh_link) {
417 		if (rh->rh_rwlock == prwlock) {
418 			if (rwt == RWT_READ)
419 				rh->rh_rdcount++;
420 			else if (rwt == RWT_WRITE)
421 				rh->rh_wrcount++;
422 			return (0);
423 		}
424 	}
425 
426 	/*
427 	 * This is the first time we're holding this lock,
428 	 * create a new entry.
429 	 */
430 	rh = (struct rwlock_held *)malloc(sizeof(struct rwlock_held));
431 	if (rh == NULL)
432 		return (ENOMEM);
433 	rh->rh_rwlock = prwlock;
434 	rh->rh_rdcount = 0;
435 	rh->rh_wrcount = 0;
436 	if (rwt == RWT_READ)
437 		rh->rh_rdcount = 1;
438 	else if (rwt == RWT_WRITE)
439 		rh->rh_wrcount = 1;
440 	LIST_INSERT_HEAD(curthread->rwlockList, rh, rh_link);
441 	return (0);
442 }
443