xref: /freebsd/lib/libthr/thread/thr_rwlock.c (revision 342af4d5efec74bb4bc11261fdd9991c53616f54)
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$
27  */
28 
29 #include <errno.h>
30 #include <limits.h>
31 #include <stdlib.h>
32 
33 #include "namespace.h"
34 #include <pthread.h>
35 #include "un-namespace.h"
36 #include "thr_private.h"
37 
38 __weak_reference(_pthread_rwlock_destroy, pthread_rwlock_destroy);
39 __weak_reference(_pthread_rwlock_init, pthread_rwlock_init);
40 __weak_reference(_pthread_rwlock_rdlock, pthread_rwlock_rdlock);
41 __weak_reference(_pthread_rwlock_timedrdlock, pthread_rwlock_timedrdlock);
42 __weak_reference(_pthread_rwlock_tryrdlock, pthread_rwlock_tryrdlock);
43 __weak_reference(_pthread_rwlock_trywrlock, pthread_rwlock_trywrlock);
44 __weak_reference(_pthread_rwlock_unlock, pthread_rwlock_unlock);
45 __weak_reference(_pthread_rwlock_wrlock, pthread_rwlock_wrlock);
46 __weak_reference(_pthread_rwlock_timedwrlock, pthread_rwlock_timedwrlock);
47 
48 #define CHECK_AND_INIT_RWLOCK							\
49 	if (*rwlock == THR_PSHARED_PTR) {					\
50 		prwlock = __thr_pshared_offpage(rwlock, 0);			\
51 		if (prwlock == NULL)						\
52 			return (EINVAL);					\
53 	} else if (__predict_false((prwlock = (*rwlock)) <=			\
54 	    THR_RWLOCK_DESTROYED)) {						\
55 		if (prwlock == THR_RWLOCK_INITIALIZER) {			\
56 			int ret;						\
57 			ret = init_static(_get_curthread(), rwlock);		\
58 			if (ret)						\
59 				return (ret);					\
60 		} else if (prwlock == THR_RWLOCK_DESTROYED) {			\
61 			return (EINVAL);					\
62 		}								\
63 		prwlock = *rwlock;						\
64 	}
65 
66 /*
67  * Prototypes
68  */
69 
70 static int
71 rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
72 {
73 	pthread_rwlock_t prwlock;
74 
75 	if (attr == NULL || *attr == NULL ||
76 	    (*attr)->pshared == PTHREAD_PROCESS_PRIVATE) {
77 		prwlock = calloc(1, sizeof(struct pthread_rwlock));
78 		if (prwlock == NULL)
79 			return (ENOMEM);
80 		*rwlock = prwlock;
81 	} else {
82 		prwlock = __thr_pshared_offpage(rwlock, 1);
83 		if (prwlock == NULL)
84 			return (EFAULT);
85 		prwlock->lock.rw_flags |= USYNC_PROCESS_SHARED;
86 		*rwlock = THR_PSHARED_PTR;
87 	}
88 	return (0);
89 }
90 
91 int
92 _pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
93 {
94 	pthread_rwlock_t prwlock;
95 	int ret;
96 
97 	prwlock = *rwlock;
98 	if (prwlock == THR_RWLOCK_INITIALIZER)
99 		ret = 0;
100 	else if (prwlock == THR_RWLOCK_DESTROYED)
101 		ret = EINVAL;
102 	else if (prwlock == THR_PSHARED_PTR) {
103 		*rwlock = THR_RWLOCK_DESTROYED;
104 		__thr_pshared_destroy(rwlock);
105 		ret = 0;
106 	} else {
107 		*rwlock = THR_RWLOCK_DESTROYED;
108 		free(prwlock);
109 		ret = 0;
110 	}
111 	return (ret);
112 }
113 
114 static int
115 init_static(struct pthread *thread, pthread_rwlock_t *rwlock)
116 {
117 	int ret;
118 
119 	THR_LOCK_ACQUIRE(thread, &_rwlock_static_lock);
120 
121 	if (*rwlock == THR_RWLOCK_INITIALIZER)
122 		ret = rwlock_init(rwlock, NULL);
123 	else
124 		ret = 0;
125 
126 	THR_LOCK_RELEASE(thread, &_rwlock_static_lock);
127 
128 	return (ret);
129 }
130 
131 int
132 _pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
133 {
134 
135 	*rwlock = NULL;
136 	return (rwlock_init(rwlock, attr));
137 }
138 
139 static int
140 rwlock_rdlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime)
141 {
142 	struct pthread *curthread = _get_curthread();
143 	pthread_rwlock_t prwlock;
144 	int flags;
145 	int ret;
146 
147 	CHECK_AND_INIT_RWLOCK
148 
149 	if (curthread->rdlock_count) {
150 		/*
151 		 * To avoid having to track all the rdlocks held by
152 		 * a thread or all of the threads that hold a rdlock,
153 		 * we keep a simple count of all the rdlocks held by
154 		 * a thread.  If a thread holds any rdlocks it is
155 		 * possible that it is attempting to take a recursive
156 		 * rdlock.  If there are blocked writers and precedence
157 		 * is given to them, then that would result in the thread
158 		 * deadlocking.  So allowing a thread to take the rdlock
159 		 * when it already has one or more rdlocks avoids the
160 		 * deadlock.  I hope the reader can follow that logic ;-)
161 		 */
162 		flags = URWLOCK_PREFER_READER;
163 	} else {
164 		flags = 0;
165 	}
166 
167 	/*
168 	 * POSIX said the validity of the abstimeout parameter need
169 	 * not be checked if the lock can be immediately acquired.
170 	 */
171 	ret = _thr_rwlock_tryrdlock(&prwlock->lock, flags);
172 	if (ret == 0) {
173 		curthread->rdlock_count++;
174 		return (ret);
175 	}
176 
177 	if (__predict_false(abstime &&
178 		(abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0)))
179 		return (EINVAL);
180 
181 	for (;;) {
182 		/* goto kernel and lock it */
183 		ret = __thr_rwlock_rdlock(&prwlock->lock, flags, abstime);
184 		if (ret != EINTR)
185 			break;
186 
187 		/* if interrupted, try to lock it in userland again. */
188 		if (_thr_rwlock_tryrdlock(&prwlock->lock, flags) == 0) {
189 			ret = 0;
190 			break;
191 		}
192 	}
193 	if (ret == 0)
194 		curthread->rdlock_count++;
195 	return (ret);
196 }
197 
198 int
199 _pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
200 {
201 	return (rwlock_rdlock_common(rwlock, NULL));
202 }
203 
204 int
205 _pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
206 	 const struct timespec *abstime)
207 {
208 	return (rwlock_rdlock_common(rwlock, abstime));
209 }
210 
211 int
212 _pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
213 {
214 	struct pthread *curthread = _get_curthread();
215 	pthread_rwlock_t prwlock;
216 	int flags;
217 	int ret;
218 
219 	CHECK_AND_INIT_RWLOCK
220 
221 	if (curthread->rdlock_count) {
222 		/*
223 		 * To avoid having to track all the rdlocks held by
224 		 * a thread or all of the threads that hold a rdlock,
225 		 * we keep a simple count of all the rdlocks held by
226 		 * a thread.  If a thread holds any rdlocks it is
227 		 * possible that it is attempting to take a recursive
228 		 * rdlock.  If there are blocked writers and precedence
229 		 * is given to them, then that would result in the thread
230 		 * deadlocking.  So allowing a thread to take the rdlock
231 		 * when it already has one or more rdlocks avoids the
232 		 * deadlock.  I hope the reader can follow that logic ;-)
233 		 */
234 		flags = URWLOCK_PREFER_READER;
235 	} else {
236 		flags = 0;
237 	}
238 
239 	ret = _thr_rwlock_tryrdlock(&prwlock->lock, flags);
240 	if (ret == 0)
241 		curthread->rdlock_count++;
242 	return (ret);
243 }
244 
245 int
246 _pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock)
247 {
248 	struct pthread *curthread = _get_curthread();
249 	pthread_rwlock_t prwlock;
250 	int ret;
251 
252 	CHECK_AND_INIT_RWLOCK
253 
254 	ret = _thr_rwlock_trywrlock(&prwlock->lock);
255 	if (ret == 0)
256 		prwlock->owner = TID(curthread);
257 	return (ret);
258 }
259 
260 static int
261 rwlock_wrlock_common (pthread_rwlock_t *rwlock, const struct timespec *abstime)
262 {
263 	struct pthread *curthread = _get_curthread();
264 	pthread_rwlock_t prwlock;
265 	int ret;
266 
267 	CHECK_AND_INIT_RWLOCK
268 
269 	/*
270 	 * POSIX said the validity of the abstimeout parameter need
271 	 * not be checked if the lock can be immediately acquired.
272 	 */
273 	ret = _thr_rwlock_trywrlock(&prwlock->lock);
274 	if (ret == 0) {
275 		prwlock->owner = TID(curthread);
276 		return (ret);
277 	}
278 
279 	if (__predict_false(abstime &&
280 	    (abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0)))
281 		return (EINVAL);
282 
283 	for (;;) {
284 		/* goto kernel and lock it */
285 		ret = __thr_rwlock_wrlock(&prwlock->lock, abstime);
286 		if (ret == 0) {
287 			prwlock->owner = TID(curthread);
288 			break;
289 		}
290 
291 		if (ret != EINTR)
292 			break;
293 
294 		/* if interrupted, try to lock it in userland again. */
295 		if (_thr_rwlock_trywrlock(&prwlock->lock) == 0) {
296 			ret = 0;
297 			prwlock->owner = TID(curthread);
298 			break;
299 		}
300 	}
301 	return (ret);
302 }
303 
304 int
305 _pthread_rwlock_wrlock (pthread_rwlock_t *rwlock)
306 {
307 	return (rwlock_wrlock_common (rwlock, NULL));
308 }
309 
310 int
311 _pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
312     const struct timespec *abstime)
313 {
314 	return (rwlock_wrlock_common (rwlock, abstime));
315 }
316 
317 int
318 _pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
319 {
320 	struct pthread *curthread = _get_curthread();
321 	pthread_rwlock_t prwlock;
322 	int ret;
323 	int32_t state;
324 
325 	if (*rwlock == THR_PSHARED_PTR) {
326 		prwlock = __thr_pshared_offpage(rwlock, 0);
327 		if (prwlock == NULL)
328 			return (EINVAL);
329 	} else {
330 		prwlock = *rwlock;
331 	}
332 
333 	if (__predict_false(prwlock <= THR_RWLOCK_DESTROYED))
334 		return (EINVAL);
335 
336 	state = prwlock->lock.rw_state;
337 	if (state & URWLOCK_WRITE_OWNER) {
338 		if (__predict_false(prwlock->owner != TID(curthread)))
339 			return (EPERM);
340 		prwlock->owner = 0;
341 	}
342 
343 	ret = _thr_rwlock_unlock(&prwlock->lock);
344 	if (ret == 0 && (state & URWLOCK_WRITE_OWNER) == 0)
345 		curthread->rdlock_count--;
346 
347 	return (ret);
348 }
349