xref: /openbsd/lib/librthread/rthread_rwlock.c (revision 9b7c3dbb)
1 /*	$OpenBSD: rthread_rwlock.c,v 1.6 2016/04/02 19:56:53 guenther Exp $ */
2 /*
3  * Copyright (c) 2004,2005 Ted Unangst <tedu@openbsd.org>
4  * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
5  * All Rights Reserved.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 /*
20  * rwlocks
21  */
22 
23 
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <errno.h>
28 
29 #include <pthread.h>
30 
31 #include "rthread.h"
32 
33 
34 static struct _spinlock rwlock_init_lock = _SPINLOCK_UNLOCKED;
35 
36 int
37 pthread_rwlock_init(pthread_rwlock_t *lockp,
38     const pthread_rwlockattr_t *attrp __unused)
39 {
40 	pthread_rwlock_t lock;
41 
42 	lock = calloc(1, sizeof(*lock));
43 	if (!lock)
44 		return (errno);
45 	lock->lock = _SPINLOCK_UNLOCKED_ASSIGN;
46 	TAILQ_INIT(&lock->writers);
47 
48 	*lockp = lock;
49 
50 	return (0);
51 }
52 DEF_STD(pthread_rwlock_init);
53 
54 int
55 pthread_rwlock_destroy(pthread_rwlock_t *lockp)
56 {
57 	pthread_rwlock_t lock;
58 
59 	assert(lockp);
60 	lock = *lockp;
61 	if (lock) {
62 		if (lock->readers || !TAILQ_EMPTY(&lock->writers)) {
63 #define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
64 			write(2, MSG, sizeof(MSG) - 1);
65 #undef MSG
66 			return (EBUSY);
67 		}
68 		free(lock);
69 	}
70 	*lockp = NULL;
71 
72 	return (0);
73 }
74 
75 static int
76 _rthread_rwlock_ensure_init(pthread_rwlock_t *lockp)
77 {
78 	int ret = 0;
79 
80 	/*
81 	 * If the rwlock is statically initialized, perform the dynamic
82 	 * initialization.
83 	 */
84 	if (*lockp == NULL)
85 	{
86 		_spinlock(&rwlock_init_lock);
87 		if (*lockp == NULL)
88 			ret = pthread_rwlock_init(lockp, NULL);
89 		_spinunlock(&rwlock_init_lock);
90 	}
91 	return (ret);
92 }
93 
94 
95 static int
96 _rthread_rwlock_rdlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
97     int try)
98 {
99 	pthread_rwlock_t lock;
100 	pthread_t thread = pthread_self();
101 	int error;
102 
103 	if ((error = _rthread_rwlock_ensure_init(lockp)))
104 		return (error);
105 
106 	lock = *lockp;
107 	_rthread_debug(5, "%p: rwlock_rdlock %p\n", (void *)thread,
108 	    (void *)lock);
109 	_spinlock(&lock->lock);
110 
111 	/* writers have precedence */
112 	if (lock->owner == NULL && TAILQ_EMPTY(&lock->writers))
113 		lock->readers++;
114 	else if (try)
115 		error = EBUSY;
116 	else if (lock->owner == thread)
117 		error = EDEADLK;
118 	else {
119 		do {
120 			if (__thrsleep(lock, CLOCK_REALTIME | _USING_TICKETS,
121 			    abstime, &lock->lock.ticket, NULL) == EWOULDBLOCK)
122 				return (ETIMEDOUT);
123 			_spinlock(&lock->lock);
124 		} while (lock->owner != NULL || !TAILQ_EMPTY(&lock->writers));
125 		lock->readers++;
126 	}
127 	_spinunlock(&lock->lock);
128 
129 	return (error);
130 }
131 
132 int
133 pthread_rwlock_rdlock(pthread_rwlock_t *lockp)
134 {
135 	return (_rthread_rwlock_rdlock(lockp, NULL, 0));
136 }
137 
138 int
139 pthread_rwlock_tryrdlock(pthread_rwlock_t *lockp)
140 {
141 	return (_rthread_rwlock_rdlock(lockp, NULL, 1));
142 }
143 
144 int
145 pthread_rwlock_timedrdlock(pthread_rwlock_t *lockp,
146     const struct timespec *abstime)
147 {
148 	if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
149 	    abstime->tv_nsec > 1000000000)
150 		return (EINVAL);
151 	return (_rthread_rwlock_rdlock(lockp, abstime, 0));
152 }
153 
154 
155 static int
156 _rthread_rwlock_wrlock(pthread_rwlock_t *lockp, const struct timespec *abstime,
157     int try)
158 {
159 	pthread_rwlock_t lock;
160 	pthread_t thread = pthread_self();
161 	int error;
162 
163 	if ((error = _rthread_rwlock_ensure_init(lockp)))
164 		return (error);
165 
166 	lock = *lockp;
167 
168 	_rthread_debug(5, "%p: rwlock_timedwrlock %p\n", (void *)thread,
169 	    (void *)lock);
170 	_spinlock(&lock->lock);
171 	if (lock->readers == 0 && lock->owner == NULL)
172 		lock->owner = thread;
173 	else if (try)
174 		error = EBUSY;
175 	else if (lock->owner == thread)
176 		error = EDEADLK;
177 	else {
178 		int do_wait;
179 
180 		/* gotta block */
181 		TAILQ_INSERT_TAIL(&lock->writers, thread, waiting);
182 		do {
183 			do_wait = __thrsleep(thread, CLOCK_REALTIME |
184 			    _USING_TICKETS, abstime,
185 			    &lock->lock.ticket, NULL) != EWOULDBLOCK;
186 			_spinlock(&lock->lock);
187 		} while (lock->owner != thread && do_wait);
188 
189 		if (lock->owner != thread) {
190 			/* timed out, sigh */
191 			TAILQ_REMOVE(&lock->writers, thread, waiting);
192 			error = ETIMEDOUT;
193 		}
194 	}
195 	_spinunlock(&lock->lock);
196 
197 	return (error);
198 }
199 
200 int
201 pthread_rwlock_wrlock(pthread_rwlock_t *lockp)
202 {
203 	return (_rthread_rwlock_wrlock(lockp, NULL, 0));
204 }
205 
206 int
207 pthread_rwlock_trywrlock(pthread_rwlock_t *lockp)
208 {
209 	return (_rthread_rwlock_wrlock(lockp, NULL, 1));
210 }
211 
212 int
213 pthread_rwlock_timedwrlock(pthread_rwlock_t *lockp,
214     const struct timespec *abstime)
215 {
216 	if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 ||
217 	    abstime->tv_nsec > 1000000000)
218 		return (EINVAL);
219 	return (_rthread_rwlock_wrlock(lockp, abstime, 0));
220 }
221 
222 
223 int
224 pthread_rwlock_unlock(pthread_rwlock_t *lockp)
225 {
226 	pthread_rwlock_t lock;
227 	pthread_t thread = pthread_self();
228 	pthread_t next;
229 	int was_writer;
230 
231 	lock = *lockp;
232 
233 	_rthread_debug(5, "%p: rwlock_unlock %p\n", (void *)thread,
234 	    (void *)lock);
235 	_spinlock(&lock->lock);
236 	if (lock->owner != NULL) {
237 		assert(lock->owner == thread);
238 		was_writer = 1;
239 	} else {
240 		assert(lock->readers > 0);
241 		lock->readers--;
242 		if (lock->readers > 0)
243 			goto out;
244 		was_writer = 0;
245 	}
246 
247 	lock->owner = next = TAILQ_FIRST(&lock->writers);
248 	if (next != NULL) {
249 		/* dequeue and wake first writer */
250 		TAILQ_REMOVE(&lock->writers, next, waiting);
251 		_spinunlock(&lock->lock);
252 		__thrwakeup(next, 1);
253 		return (0);
254 	}
255 
256 	/* could there have been blocked readers?  wake them all */
257 	if (was_writer)
258 		__thrwakeup(lock, 0);
259 out:
260 	_spinunlock(&lock->lock);
261 
262 	return (0);
263 }
264