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