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