1 /*
2 * This code is from the book "Programming with POSIX Threads", by
3 * David R. Butenhof.
4 */
5
6 /*
7 * rwlock.c
8 *
9 * This file implements the "read-write lock" synchronization
10 * construct.
11 *
12 * A read-write lock allows a thread to lock shared data either
13 * for shared read access or exclusive write access.
14 *
15 * The rwl_init() and rwl_destroy() functions, respectively,
16 * allow you to initialize/create and destroy/free the
17 * read-write lock.
18 *
19 * The rwl_rdlock() function locks a read-write lock for
20 * shared read access, and rwl_rdunlock() releases the
21 * lock. rwl_tryrdlock() attempts to lock a read-write lock
22 * for read access, and returns EBUSY instead of blocking.
23 *
24 * The rwl_wrlock() function locks a read-write lock for
25 * exclusive write access, and rwl_wrunlock() releases the
26 * lock. rwl_trywrlock() attempts to lock a read-write lock
27 * for write access, and returns EBUSY instead of blocking.
28 */
29 #include <pthread.h>
30 #include <errno.h>
31 #include "rwlock.h"
32
33 #ifndef PTHREAD_RWLOCK_INITIALIZER
34 // #ifndef HAVE_PTHREAD_RWLOCK
35
36 /*
37 * Initialize a read-write lock
38 */
rwl_init(rwlock_t * rwl)39 int rwl_init (rwlock_t *rwl)
40 {
41 int status;
42
43 rwl->r_active = 0;
44 rwl->r_wait = rwl->w_wait = 0;
45 rwl->w_active = 0;
46 status = pthread_mutex_init (&rwl->mutex, NULL);
47 if (status != 0)
48 return status;
49 status = pthread_cond_init (&rwl->read, NULL);
50 if (status != 0) {
51 /* if unable to create read CV, destroy mutex */
52 pthread_mutex_destroy (&rwl->mutex);
53 return status;
54 }
55 status = pthread_cond_init (&rwl->write, NULL);
56 if (status != 0) {
57 /* if unable to create write CV, destroy read CV and mutex */
58 pthread_cond_destroy (&rwl->read);
59 pthread_mutex_destroy (&rwl->mutex);
60 return status;
61 }
62 rwl->valid = RWLOCK_VALID;
63 return 0;
64 }
65
66 /*
67 * Destroy a read-write lock
68 */
rwl_destroy(rwlock_t * rwl)69 int rwl_destroy (rwlock_t *rwl)
70 {
71 int status, status1, status2;
72
73 if (rwl->valid != RWLOCK_VALID)
74 return EINVAL;
75 status = pthread_mutex_lock (&rwl->mutex);
76 if (status != 0)
77 return status;
78
79 /*
80 * Check whether any threads own the lock; report "BUSY" if
81 * so.
82 */
83 if (rwl->r_active > 0 || rwl->w_active) {
84 pthread_mutex_unlock (&rwl->mutex);
85 return EBUSY;
86 }
87
88 /*
89 * Check whether any threads are known to be waiting; report
90 * EBUSY if so.
91 */
92 if (rwl->r_wait != 0 || rwl->w_wait != 0) {
93 pthread_mutex_unlock (&rwl->mutex);
94 return EBUSY;
95 }
96
97 rwl->valid = 0;
98 status = pthread_mutex_unlock (&rwl->mutex);
99 if (status != 0)
100 return status;
101 status = pthread_mutex_destroy (&rwl->mutex);
102 status1 = pthread_cond_destroy (&rwl->read);
103 status2 = pthread_cond_destroy (&rwl->write);
104 return (status == 0 ? status : (status1 == 0 ? status1 : status2));
105 }
106
107 /*
108 * Handle cleanup when the read lock condition variable
109 * wait is cancelled.
110 *
111 * Simply record that the thread is no longer waiting,
112 * and unlock the mutex.
113 */
rwl_readcleanup(void * arg)114 static void rwl_readcleanup (void *arg)
115 {
116 rwlock_t *rwl = (rwlock_t *)arg;
117
118 rwl->r_wait--;
119 pthread_mutex_unlock (&rwl->mutex);
120 }
121
122 /*
123 * Lock a read-write lock for read access.
124 */
rwl_rdlock(rwlock_t * rwl)125 int rwl_rdlock (rwlock_t *rwl)
126 {
127 int status;
128
129 if (rwl->valid != RWLOCK_VALID)
130 return EINVAL;
131 status = pthread_mutex_lock (&rwl->mutex);
132 if (status != 0)
133 return status;
134 if (rwl->w_active) {
135 rwl->r_wait++;
136 pthread_cleanup_push (rwl_readcleanup, (void*)rwl);
137 while (rwl->w_active) {
138 status = pthread_cond_wait (&rwl->read, &rwl->mutex);
139 if (status != 0)
140 break;
141 }
142 pthread_cleanup_pop (0);
143 rwl->r_wait--;
144 }
145 if (status == 0)
146 rwl->r_active++;
147 pthread_mutex_unlock (&rwl->mutex);
148 return status;
149 }
150
151 /*
152 * Attempt to lock a read-write lock for read access (don't
153 * block if unavailable).
154 */
rwl_tryrdlock(rwlock_t * rwl)155 int rwl_tryrdlock (rwlock_t *rwl)
156 {
157 int status, status2;
158
159 if (rwl->valid != RWLOCK_VALID)
160 return EINVAL;
161 status = pthread_mutex_lock (&rwl->mutex);
162 if (status != 0)
163 return status;
164 if (rwl->w_active)
165 status = EBUSY;
166 else
167 rwl->r_active++;
168 status2 = pthread_mutex_unlock (&rwl->mutex);
169 return (status2 != 0 ? status2 : status);
170 }
171
172 /*
173 * Unlock a read-write lock from read access.
174 */
rwl_rdunlock(rwlock_t * rwl)175 int rwl_rdunlock (rwlock_t *rwl)
176 {
177 int status, status2;
178
179 if (rwl->valid != RWLOCK_VALID)
180 return EINVAL;
181 status = pthread_mutex_lock (&rwl->mutex);
182 if (status != 0)
183 return status;
184 rwl->r_active--;
185 if (rwl->r_active == 0 && rwl->w_wait > 0)
186 status = pthread_cond_signal (&rwl->write);
187 status2 = pthread_mutex_unlock (&rwl->mutex);
188 return (status2 == 0 ? status : status2);
189 }
190
191 /*
192 * Handle cleanup when the write lock condition variable
193 * wait is cancelled.
194 *
195 * Simply record that the thread is no longer waiting,
196 * and unlock the mutex.
197 */
rwl_writecleanup(void * arg)198 static void rwl_writecleanup (void *arg)
199 {
200 rwlock_t *rwl = (rwlock_t *)arg;
201
202 rwl->w_wait--;
203 pthread_mutex_unlock (&rwl->mutex);
204 }
205
206 /*
207 * Lock a read-write lock for write access.
208 */
rwl_wrlock(rwlock_t * rwl)209 int rwl_wrlock (rwlock_t *rwl)
210 {
211 int status;
212
213 if (rwl->valid != RWLOCK_VALID)
214 return EINVAL;
215 status = pthread_mutex_lock (&rwl->mutex);
216 if (status != 0)
217 return status;
218 if (rwl->w_active || rwl->r_active > 0) {
219 rwl->w_wait++;
220 pthread_cleanup_push (rwl_writecleanup, (void*)rwl);
221 while (rwl->w_active || rwl->r_active > 0) {
222 status = pthread_cond_wait (&rwl->write, &rwl->mutex);
223 if (status != 0)
224 break;
225 }
226 pthread_cleanup_pop (0);
227 rwl->w_wait--;
228 }
229 if (status == 0)
230 rwl->w_active = 1;
231 pthread_mutex_unlock (&rwl->mutex);
232 return status;
233 }
234
235 /*
236 * Attempt to lock a read-write lock for write access. Don't
237 * block if unavailable.
238 */
rwl_trywrlock(rwlock_t * rwl)239 int rwl_trywrlock (rwlock_t *rwl)
240 {
241 int status, status2;
242
243 if (rwl->valid != RWLOCK_VALID)
244 return EINVAL;
245 status = pthread_mutex_lock (&rwl->mutex);
246 if (status != 0)
247 return status;
248 if (rwl->w_active || rwl->r_active > 0)
249 status = EBUSY;
250 else
251 rwl->w_active = 1;
252 status2 = pthread_mutex_unlock (&rwl->mutex);
253 return (status != 0 ? status : status2);
254 }
255
256 /*
257 * Unlock a read-write lock from write access.
258 */
rwl_wrunlock(rwlock_t * rwl)259 int rwl_wrunlock (rwlock_t *rwl)
260 {
261 int status;
262
263 if (rwl->valid != RWLOCK_VALID)
264 return EINVAL;
265 status = pthread_mutex_lock (&rwl->mutex);
266 if (status != 0)
267 return status;
268 rwl->w_active = 0;
269 if (rwl->r_wait > 0) {
270 status = pthread_cond_broadcast (&rwl->read);
271 if (status != 0) {
272 pthread_mutex_unlock (&rwl->mutex);
273 return status;
274 }
275 } else if (rwl->w_wait > 0) {
276 status = pthread_cond_signal (&rwl->write);
277 if (status != 0) {
278 pthread_mutex_unlock (&rwl->mutex);
279 return status;
280 }
281 }
282 status = pthread_mutex_unlock (&rwl->mutex);
283 return status;
284 }
285
286 #endif /* ifndef HAVE_POSIX_RWLOCK */
287