xref: /openbsd/lib/librthread/rthread_rwlock.c (revision b6c30fbb)
1 /*	$OpenBSD: rthread_rwlock.c,v 1.13 2019/03/03 18:39:10 visa Exp $ */
2 /*
3  * Copyright (c) 2019 Martin Pieuchot <mpi@openbsd.org>
4  * Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <errno.h>
22 
23 #include <pthread.h>
24 
25 #include "rthread.h"
26 #include "synch.h"
27 
28 #define UNLOCKED	0
29 #define MAXREADER	0x7ffffffe
30 #define WRITER		0x7fffffff
31 #define WAITING		0x80000000
32 #define COUNT(v)	((v) & WRITER)
33 
34 #define SPIN_COUNT	128
35 #if defined(__i386__) || defined(__amd64__)
36 #define SPIN_WAIT()	asm volatile("pause": : : "memory")
37 #else
38 #define SPIN_WAIT()	do { } while (0)
39 #endif
40 
41 static _atomic_lock_t rwlock_init_lock = _SPINLOCK_UNLOCKED;
42 
43 int
pthread_rwlock_init(pthread_rwlock_t * lockp,const pthread_rwlockattr_t * attrp __unused)44 pthread_rwlock_init(pthread_rwlock_t *lockp,
45     const pthread_rwlockattr_t *attrp __unused)
46 {
47 	pthread_rwlock_t rwlock;
48 
49 	rwlock = calloc(1, sizeof(*rwlock));
50 	if (!rwlock)
51 		return (errno);
52 
53 	*lockp = rwlock;
54 
55 	return (0);
56 }
57 DEF_STD(pthread_rwlock_init);
58 
59 int
pthread_rwlock_destroy(pthread_rwlock_t * lockp)60 pthread_rwlock_destroy(pthread_rwlock_t *lockp)
61 {
62 	pthread_rwlock_t rwlock;
63 
64 	rwlock = *lockp;
65 	if (rwlock) {
66 		if (rwlock->value != UNLOCKED) {
67 #define MSG "pthread_rwlock_destroy on rwlock with waiters!\n"
68 			write(2, MSG, sizeof(MSG) - 1);
69 #undef MSG
70 			return (EBUSY);
71 		}
72 		free((void *)rwlock);
73 		*lockp = NULL;
74 	}
75 
76 	return (0);
77 }
78 
79 static int
_rthread_rwlock_ensure_init(pthread_rwlock_t * rwlockp)80 _rthread_rwlock_ensure_init(pthread_rwlock_t *rwlockp)
81 {
82 	int ret = 0;
83 
84 	/*
85 	 * If the rwlock is statically initialized, perform the dynamic
86 	 * initialization.
87 	 */
88 	if (*rwlockp == NULL) {
89 		_spinlock(&rwlock_init_lock);
90 		if (*rwlockp == NULL)
91 			ret = pthread_rwlock_init(rwlockp, NULL);
92 		_spinunlock(&rwlock_init_lock);
93 	}
94 	return (ret);
95 }
96 
97 static int
_rthread_rwlock_tryrdlock(pthread_rwlock_t rwlock)98 _rthread_rwlock_tryrdlock(pthread_rwlock_t rwlock)
99 {
100 	unsigned int val;
101 
102 	do {
103 		val = rwlock->value;
104 		if (COUNT(val) == WRITER)
105 			return (EBUSY);
106 		if (COUNT(val) == MAXREADER)
107 			return (EAGAIN);
108 	} while (atomic_cas_uint(&rwlock->value, val, val + 1) != val);
109 
110 	membar_enter_after_atomic();
111 	return (0);
112 }
113 
114 static int
_rthread_rwlock_timedrdlock(pthread_rwlock_t * rwlockp,int trywait,const struct timespec * abs,int timed)115 _rthread_rwlock_timedrdlock(pthread_rwlock_t *rwlockp, int trywait,
116     const struct timespec *abs, int timed)
117 {
118 	pthread_t self = pthread_self();
119 	pthread_rwlock_t rwlock;
120 	unsigned int val, new;
121 	int i, error;
122 
123 	if ((error = _rthread_rwlock_ensure_init(rwlockp)))
124 		return (error);
125 
126 	rwlock = *rwlockp;
127 	_rthread_debug(5, "%p: rwlock_%srdlock %p (%u)\n", self,
128 	    (timed ? "timed" : (trywait ? "try" : "")), (void *)rwlock,
129 	    rwlock->value);
130 
131 	error = _rthread_rwlock_tryrdlock(rwlock);
132 	if (error != EBUSY || trywait)
133 		return (error);
134 
135 	/* Try hard to not enter the kernel. */
136 	for (i = 0; i < SPIN_COUNT; i++) {
137 		val = rwlock->value;
138 		if (val == UNLOCKED || (val & WAITING))
139 			break;
140 
141 		SPIN_WAIT();
142 	}
143 
144 	while ((error = _rthread_rwlock_tryrdlock(rwlock)) == EBUSY) {
145 		val = rwlock->value;
146 		if (val == UNLOCKED || (COUNT(val)) != WRITER)
147 			continue;
148 		new = val | WAITING;
149 		if (atomic_cas_uint(&rwlock->value, val, new) == val) {
150 			error = _twait(&rwlock->value, new, CLOCK_REALTIME,
151 			    abs);
152 		}
153 		if (error == ETIMEDOUT)
154 			break;
155 	}
156 
157 	return (error);
158 
159 }
160 
161 int
pthread_rwlock_tryrdlock(pthread_rwlock_t * rwlockp)162 pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlockp)
163 {
164 	return (_rthread_rwlock_timedrdlock(rwlockp, 1, NULL, 0));
165 }
166 
167 int
pthread_rwlock_timedrdlock(pthread_rwlock_t * rwlockp,const struct timespec * abs)168 pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlockp,
169     const struct timespec *abs)
170 {
171 	return (_rthread_rwlock_timedrdlock(rwlockp, 0, abs, 1));
172 }
173 
174 int
pthread_rwlock_rdlock(pthread_rwlock_t * rwlockp)175 pthread_rwlock_rdlock(pthread_rwlock_t *rwlockp)
176 {
177 	return (_rthread_rwlock_timedrdlock(rwlockp, 0, NULL, 0));
178 }
179 
180 static int
_rthread_rwlock_tryrwlock(pthread_rwlock_t rwlock)181 _rthread_rwlock_tryrwlock(pthread_rwlock_t rwlock)
182 {
183 	if (atomic_cas_uint(&rwlock->value, UNLOCKED, WRITER) != UNLOCKED)
184 		return (EBUSY);
185 
186 	membar_enter_after_atomic();
187 	return (0);
188 }
189 
190 
191 static int
_rthread_rwlock_timedwrlock(pthread_rwlock_t * rwlockp,int trywait,const struct timespec * abs,int timed)192 _rthread_rwlock_timedwrlock(pthread_rwlock_t *rwlockp, int trywait,
193     const struct timespec *abs, int timed)
194 {
195 	pthread_t self = pthread_self();
196 	pthread_rwlock_t rwlock;
197 	unsigned int val, new;
198 	int i, error;
199 
200 	if ((error = _rthread_rwlock_ensure_init(rwlockp)))
201 		return (error);
202 
203 	rwlock = *rwlockp;
204 	_rthread_debug(5, "%p: rwlock_%swrlock %p (%u)\n", self,
205 	    (timed ? "timed" : (trywait ? "try" : "")), (void *)rwlock,
206 	    rwlock->value);
207 
208 	error = _rthread_rwlock_tryrwlock(rwlock);
209 	if (error != EBUSY || trywait)
210 		return (error);
211 
212 	/* Try hard to not enter the kernel. */
213 	for (i = 0; i < SPIN_COUNT; i++) {
214 		val = rwlock->value;
215 		if (val == UNLOCKED || (val & WAITING))
216 			break;
217 
218 		SPIN_WAIT();
219 	}
220 
221 	while ((error = _rthread_rwlock_tryrwlock(rwlock)) == EBUSY) {
222 		val = rwlock->value;
223 		if (val == UNLOCKED)
224 			continue;
225 		new = val | WAITING;
226 		if (atomic_cas_uint(&rwlock->value, val, new) == val) {
227 			error = _twait(&rwlock->value, new, CLOCK_REALTIME,
228 			    abs);
229 		}
230 		if (error == ETIMEDOUT)
231 			break;
232 	}
233 
234 	return (error);
235 }
236 
237 int
pthread_rwlock_trywrlock(pthread_rwlock_t * rwlockp)238 pthread_rwlock_trywrlock(pthread_rwlock_t *rwlockp)
239 {
240 	return (_rthread_rwlock_timedwrlock(rwlockp, 1, NULL, 0));
241 }
242 
243 int
pthread_rwlock_timedwrlock(pthread_rwlock_t * rwlockp,const struct timespec * abs)244 pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlockp,
245     const struct timespec *abs)
246 {
247 	return (_rthread_rwlock_timedwrlock(rwlockp, 0, abs, 1));
248 }
249 
250 int
pthread_rwlock_wrlock(pthread_rwlock_t * rwlockp)251 pthread_rwlock_wrlock(pthread_rwlock_t *rwlockp)
252 {
253 	return (_rthread_rwlock_timedwrlock(rwlockp, 0, NULL, 0));
254 }
255 
256 int
pthread_rwlock_unlock(pthread_rwlock_t * rwlockp)257 pthread_rwlock_unlock(pthread_rwlock_t *rwlockp)
258 {
259 	pthread_t self = pthread_self();
260 	pthread_rwlock_t rwlock;
261 	unsigned int val, new;
262 
263 	rwlock = *rwlockp;
264 	_rthread_debug(5, "%p: rwlock_unlock %p\n", self, (void *)rwlock);
265 
266 	membar_exit_before_atomic();
267 	do {
268 		val = rwlock->value;
269 		if (COUNT(val) == WRITER || COUNT(val) == 1)
270 			new = UNLOCKED;
271 		else
272 			new = val - 1;
273 	} while (atomic_cas_uint(&rwlock->value, val, new) != val);
274 
275 	if (new == UNLOCKED && (val & WAITING))
276 		_wake(&rwlock->value, INT_MAX);
277 
278 	return (0);
279 }
280