thr_rwlock.c revision 178194
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: head/lib/libthr/thread/thr_rwlock.c 178194 2008-04-14 08:08:40Z davidxu $
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/*
49 * Prototypes
50 */
51
52static int
53rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr __unused)
54{
55	pthread_rwlock_t prwlock;
56
57	prwlock = (pthread_rwlock_t)calloc(1, sizeof(struct pthread_rwlock));
58	if (prwlock == NULL)
59		return (ENOMEM);
60	*rwlock = prwlock;
61	return (0);
62}
63
64int
65_pthread_rwlock_destroy (pthread_rwlock_t *rwlock)
66{
67	int ret;
68
69	if (rwlock == NULL)
70		ret = EINVAL;
71	else {
72		pthread_rwlock_t prwlock;
73
74		prwlock = *rwlock;
75		*rwlock = NULL;
76
77		free(prwlock);
78		ret = 0;
79	}
80	return (ret);
81}
82
83static int
84init_static(struct pthread *thread, pthread_rwlock_t *rwlock)
85{
86	int ret;
87
88	THR_LOCK_ACQUIRE(thread, &_rwlock_static_lock);
89
90	if (*rwlock == NULL)
91		ret = rwlock_init(rwlock, NULL);
92	else
93		ret = 0;
94
95	THR_LOCK_RELEASE(thread, &_rwlock_static_lock);
96
97	return (ret);
98}
99
100int
101_pthread_rwlock_init (pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)
102{
103	*rwlock = NULL;
104	return (rwlock_init(rwlock, attr));
105}
106
107static int
108rwlock_rdlock_common(pthread_rwlock_t *rwlock, const struct timespec *abstime)
109{
110	struct pthread *curthread = _get_curthread();
111	pthread_rwlock_t prwlock;
112	struct timespec ts, ts2, *tsp;
113	int flags;
114	int ret;
115
116	if (__predict_false(rwlock == NULL))
117		return (EINVAL);
118
119	prwlock = *rwlock;
120
121	/* check for static initialization */
122	if (__predict_false(prwlock == NULL)) {
123		if ((ret = init_static(curthread, rwlock)) != 0)
124			return (ret);
125
126		prwlock = *rwlock;
127	}
128
129	if (curthread->rdlock_count) {
130		/*
131		 * To avoid having to track all the rdlocks held by
132		 * a thread or all of the threads that hold a rdlock,
133		 * we keep a simple count of all the rdlocks held by
134		 * a thread.  If a thread holds any rdlocks it is
135		 * possible that it is attempting to take a recursive
136		 * rdlock.  If there are blocked writers and precedence
137		 * is given to them, then that would result in the thread
138		 * deadlocking.  So allowing a thread to take the rdlock
139		 * when it already has one or more rdlocks avoids the
140		 * deadlock.  I hope the reader can follow that logic ;-)
141		 */
142		flags = URWLOCK_PREFER_READER;
143	} else {
144		flags = 0;
145	}
146
147	/*
148	 * POSIX said the validity of the abstimeout parameter need
149	 * not be checked if the lock can be immediately acquired.
150	 */
151	ret = _thr_rwlock_tryrdlock(&prwlock->lock, flags);
152	if (ret == 0) {
153		curthread->rdlock_count++;
154		return (ret);
155	}
156
157	if (__predict_false(abstime &&
158		(abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0)))
159		return (EINVAL);
160
161	for (;;) {
162		if (abstime) {
163			clock_gettime(CLOCK_REALTIME, &ts);
164			TIMESPEC_SUB(&ts2, abstime, &ts);
165			if (ts2.tv_sec < 0 ||
166			    (ts2.tv_sec == 0 && ts2.tv_nsec <= 0))
167				return (ETIMEDOUT);
168			tsp = &ts2;
169		} else
170			tsp = NULL;
171
172		/* goto kernel and lock it */
173		ret = __thr_rwlock_rdlock(&prwlock->lock, flags, tsp);
174		if (ret != EINTR)
175			break;
176
177		/* if interrupted, try to lock it in userland again. */
178		if (_thr_rwlock_tryrdlock(&prwlock->lock, flags) == 0) {
179			ret = 0;
180			curthread->rdlock_count++;
181			break;
182		}
183	}
184	return (ret);
185}
186
187int
188_pthread_rwlock_rdlock (pthread_rwlock_t *rwlock)
189{
190	return (rwlock_rdlock_common(rwlock, NULL));
191}
192
193int
194_pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock,
195	 const struct timespec *abstime)
196{
197	return (rwlock_rdlock_common(rwlock, abstime));
198}
199
200int
201_pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock)
202{
203	struct pthread *curthread = _get_curthread();
204	pthread_rwlock_t prwlock;
205	int flags;
206	int ret;
207
208	if (__predict_false(rwlock == NULL))
209		return (EINVAL);
210
211	prwlock = *rwlock;
212
213	/* check for static initialization */
214	if (__predict_false(prwlock == NULL)) {
215		if ((ret = init_static(curthread, rwlock)) != 0)
216			return (ret);
217
218		prwlock = *rwlock;
219	}
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
245int
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	if (__predict_false(rwlock == NULL))
253		return (EINVAL);
254
255	prwlock = *rwlock;
256
257	/* check for static initialization */
258	if (__predict_false(prwlock == NULL)) {
259		if ((ret = init_static(curthread, rwlock)) != 0)
260			return (ret);
261
262		prwlock = *rwlock;
263	}
264
265	ret = _thr_rwlock_trywrlock(&prwlock->lock);
266	if (ret == 0)
267		prwlock->owner = curthread;
268	return (ret);
269}
270
271static int
272rwlock_wrlock_common (pthread_rwlock_t *rwlock, const struct timespec *abstime)
273{
274	struct pthread *curthread = _get_curthread();
275	pthread_rwlock_t prwlock;
276	struct timespec ts, ts2, *tsp;
277	int ret;
278
279	if (__predict_false(rwlock == NULL))
280		return (EINVAL);
281
282	prwlock = *rwlock;
283
284	/* check for static initialization */
285	if (__predict_false(prwlock == NULL)) {
286		if ((ret = init_static(curthread, rwlock)) != 0)
287			return (ret);
288
289		prwlock = *rwlock;
290	}
291
292	/*
293	 * POSIX said the validity of the abstimeout parameter need
294	 * not be checked if the lock can be immediately acquired.
295	 */
296	ret = _thr_rwlock_trywrlock(&prwlock->lock);
297	if (ret == 0) {
298		prwlock->owner = curthread;
299		return (ret);
300	}
301
302	if (__predict_false(abstime &&
303		(abstime->tv_nsec >= 1000000000 || abstime->tv_nsec < 0)))
304		return (EINVAL);
305
306	for (;;) {
307		if (abstime != NULL) {
308			clock_gettime(CLOCK_REALTIME, &ts);
309			TIMESPEC_SUB(&ts2, abstime, &ts);
310			if (ts2.tv_sec < 0 ||
311			    (ts2.tv_sec == 0 && ts2.tv_nsec <= 0))
312				return (ETIMEDOUT);
313			tsp = &ts2;
314		} else
315			tsp = NULL;
316
317		/* goto kernel and lock it */
318		ret = __thr_rwlock_wrlock(&prwlock->lock, tsp);
319		if (ret == 0) {
320			prwlock->owner = curthread;
321			break;
322		}
323
324		if (ret != EINTR)
325			break;
326
327		/* if interrupted, try to lock it in userland again. */
328		if (_thr_rwlock_trywrlock(&prwlock->lock) == 0) {
329			ret = 0;
330			prwlock->owner = curthread;
331			break;
332		}
333	}
334	return (ret);
335}
336
337int
338_pthread_rwlock_wrlock (pthread_rwlock_t *rwlock)
339{
340	return (rwlock_wrlock_common (rwlock, NULL));
341}
342
343int
344_pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock,
345    const struct timespec *abstime)
346{
347	return (rwlock_wrlock_common (rwlock, abstime));
348}
349
350int
351_pthread_rwlock_unlock (pthread_rwlock_t *rwlock)
352{
353	struct pthread *curthread = _get_curthread();
354	pthread_rwlock_t prwlock;
355	int ret;
356	int32_t state;
357
358	if (__predict_false(rwlock == NULL))
359		return (EINVAL);
360
361	prwlock = *rwlock;
362
363	if (__predict_false(prwlock == NULL))
364		return (EINVAL);
365
366	state = prwlock->lock.rw_state;
367	if (state & URWLOCK_WRITE_OWNER) {
368		if (__predict_false(prwlock->owner != curthread))
369			return (EPERM);
370		prwlock->owner = NULL;
371	}
372
373	ret = _thr_rwlock_unlock(&prwlock->lock);
374	if (ret == 0 && (state & URWLOCK_WRITE_OWNER) == 0)
375		curthread->rdlock_count--;
376
377	return (ret);
378}
379