Add to git.
[pthrlib.git] / src / pthr_rwlock.c
1 /* Multiple reader / single writer locks for pthrlib.
2  * by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: pthr_rwlock.c,v 1.3 2002/12/01 14:29:30 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24
25 #ifdef HAVE_ASSERT_H
26 #include <assert.h>
27 #endif
28
29 #include <pool.h>
30 #include <hash.h>
31
32 #include "pthr_pseudothread.h"
33 #include "pthr_wait_queue.h"
34 #include "pthr_rwlock.h"
35
36 /* I added this while I was trying to pin down a possible memory corruption
37  * problem. It can be disabled in normal operations.
38  */
39 /* #define RWLOCK_MEM_DEBUG 1 */
40 #define RWLOCK_MEM_DEBUG 0
41
42 #if RWLOCK_MEM_DEBUG
43 #define RWLOCK_MEM_MAGIC 0x99775533
44 #endif
45
46 struct rwlock
47 {
48 #if RWLOCK_MEM_DEBUG
49   unsigned magic;
50 #endif
51   int n;                        /* If N == 0, lock is free.
52                                  * If N > 0, lock is held by N readers.
53                                  * If N == -1, lock is held by 1 writer.
54                                  */
55   wait_queue writers_wq;        /* Writers wait on this queue. */
56   wait_queue readers_wq;        /* Readers wait on this queue. */
57
58   /* A hash from pth pointer -> subpool. The keys of this hash are
59    * pseudothreads which are currently in the critical section. The
60    * values are subpools of the appropriate pth pool. If a thread
61    * exits without releasing the lock, then the subpool is deleted,
62    * which causes our callback to run, releasing the lock.
63    */
64   hash pools;
65
66   unsigned writers_have_priority:1;
67 };
68
69 static void _do_enter (rwlock);
70 static void _do_release (void *);
71 static void _delete_rwlock (void *);
72
73 rwlock
74 new_rwlock (pool p)
75 {
76   rwlock rw = pmalloc (p, sizeof *rw);
77
78 #if RWLOCK_MEM_DEBUG
79   rw->magic = RWLOCK_MEM_MAGIC;
80 #endif
81
82   rw->n = 0;
83   rw->readers_wq = new_wait_queue (p);
84   rw->writers_wq = new_wait_queue (p);
85   rw->writers_have_priority = 1;
86   rw->pools = new_hash (p, pseudothread, pool);
87
88   /* The purpose of this cleanup is just to check that the rwlock
89    * isn't released with threads in the critical section.
90    */
91   pool_register_cleanup_fn (p, _delete_rwlock, rw);
92
93   return rw;
94 }
95
96 static void
97 _delete_rwlock (void *vrw)
98 {
99   rwlock rw = (rwlock) vrw;
100 #if RWLOCK_MEM_DEBUG
101   assert (rw->magic == RWLOCK_MEM_MAGIC);
102 #endif
103   assert (rw->n == 0);
104 }
105
106 /* Calling this function changes the nature of the lock so that
107  * writers have priority over readers. If this is the case then
108  * new readers will not be able to enter a critical section if
109  * there are writers waiting to enter.
110  * [NB: This is the default.]
111  */
112 void
113 rwlock_writers_have_priority (rwlock rw)
114 {
115 #if RWLOCK_MEM_DEBUG
116   assert (rw->magic == RWLOCK_MEM_MAGIC);
117 #endif
118   rw->writers_have_priority = 1;
119 }
120
121 /* Calling this function changes the nature of the lock so that
122  * readers have priority over writers. Note that if this is the case
123  * then writers are likely to be starved if the lock is frequently
124  * read.
125  */
126 void
127 rwlock_readers_have_priority (rwlock rw)
128 {
129 #if RWLOCK_MEM_DEBUG
130   assert (rw->magic == RWLOCK_MEM_MAGIC);
131 #endif
132   rw->writers_have_priority = 0;
133 }
134
135 /* This function is identical to RWLOCK_ENTER_READ, but it
136  * does not block. It returns TRUE if the lock was successfully
137  * acquired, or FALSE if the operation would block.
138  */
139 inline int
140 rwlock_try_enter_read (rwlock rw)
141 {
142 #if RWLOCK_MEM_DEBUG
143   assert (rw->magic == RWLOCK_MEM_MAGIC);
144 #endif
145
146   if (rw->n >= 0 &&
147       (!rw->writers_have_priority ||
148        wq_nr_sleepers (rw->writers_wq) == 0))
149     {
150       _do_enter (rw);
151       rw->n++;
152       return 1;
153     }
154   else
155     return 0;
156 }
157
158 /* This function is identical to RWLOCK_ENTER_WRITE, but it
159  * does not block. It returns TRUE if the lock was successfully
160  * acquired, or FALSE if the operation would block.
161  */
162 inline int
163 rwlock_try_enter_write (rwlock rw)
164 {
165 #if RWLOCK_MEM_DEBUG
166   assert (rw->magic == RWLOCK_MEM_MAGIC);
167 #endif
168
169   if (rw->n == 0)
170     {
171       _do_enter (rw);
172       rw->n = -1;
173       return 1;
174     }
175   else
176     return 0;
177 }
178
179 /* Enter a critical section as a reader. Any number of readers
180  * are allowed to enter a critical section at the same time. This
181  * function may block.
182  */
183 void
184 rwlock_enter_read (rwlock rw)
185 {
186 #if RWLOCK_MEM_DEBUG
187   assert (rw->magic == RWLOCK_MEM_MAGIC);
188 #endif
189
190   while (rwlock_try_enter_read (rw) == 0)
191     wq_sleep_on (rw->readers_wq);
192 }
193
194 /* Enter a critical section as a writer. Only a single writer
195  * is allowed to enter a critical section, and then only if
196  * there are no readers. This function may block.
197  */
198 void
199 rwlock_enter_write (rwlock rw)
200 {
201 #if RWLOCK_MEM_DEBUG
202   assert (rw->magic == RWLOCK_MEM_MAGIC);
203 #endif
204
205   while (rwlock_try_enter_write (rw) == 0)
206     wq_sleep_on (rw->writers_wq);
207 }
208
209 /* Leave a critical section. */
210 void
211 rwlock_leave (rwlock rw)
212 {
213   pool pool;
214
215 #if RWLOCK_MEM_DEBUG
216   assert (rw->magic == RWLOCK_MEM_MAGIC);
217 #endif
218
219   /* If this core dumps, it's probably because the pth didn't actually
220    * hold a lock.
221    */
222   if (!hash_get (rw->pools, current_pth, pool)) abort ();
223
224   /* Force _DO_RELEASE to run. */
225   delete_pool (pool);
226 }
227
228 struct cleanup_data
229 {
230   pseudothread pth;
231   rwlock rw;
232 };
233
234 /* This function registers a clean-up function which deals with the
235  * case when a thread exits early without releasing the lock.
236  */
237 static void
238 _do_enter (rwlock rw)
239 {
240   struct cleanup_data *data;
241   pool pool;
242
243 #if RWLOCK_MEM_DEBUG
244   assert (rw->magic == RWLOCK_MEM_MAGIC);
245 #endif
246
247   /* Create a subpool. If the thread exits early, then this subpool
248    * with be deleted implicitly. If, on the other hand, we release
249    * the lock in RWLOCK_LEAVE, then we will delete this pool
250    * explicitly. Either way, _DO_RELEASE will be called.
251    */
252   pool = new_subpool (pth_get_pool (current_pth));
253
254   /* Save it in the hash. */
255   hash_insert (rw->pools, current_pth, pool);
256
257   /* Register a clean-up function in the subpool to call _DO_RELEASE. */
258   data = pmalloc (pool, sizeof (struct cleanup_data));
259   data->pth = current_pth;
260   data->rw = rw;
261   pool_register_cleanup_fn (pool, _do_release, data);
262 }
263
264 /* This function is called to do the actual work of releasing a lock. */
265 static void
266 _do_release (void *vdata)
267 {
268   struct cleanup_data *data = (struct cleanup_data *)vdata;
269   pseudothread pth = data->pth;
270   rwlock rw = data->rw;
271
272 #if RWLOCK_MEM_DEBUG
273   assert (rw->magic == RWLOCK_MEM_MAGIC);
274 #endif
275
276   assert (rw->n != 0);
277
278   /* Remove this pseudothread from rw->pools. */
279   if (!hash_erase (rw->pools, pth)) abort ();
280
281   if (rw->n > 0)                /* Reader leaving critical section? */
282     {
283       rw->n --;
284       if (rw->n == 0)
285         {
286           /* Any writers waiting? */
287           if (wq_nr_sleepers (rw->writers_wq) > 0)
288             wq_wake_up_one (rw->writers_wq);
289
290           /* This can't happen (probably). */
291           /* XXX It does happen -- but I believe it's not a mistake. */
292           /*assert (wq_nr_sleepers (rw->readers_wq) == 0);*/
293         }
294     }
295   else                          /* Writer leaving critical section? */
296     {
297       rw->n = 0;
298
299       /* Any writers waiting? */
300       if (wq_nr_sleepers (rw->writers_wq) > 0)
301         wq_wake_up_one (rw->writers_wq);
302       /* Any readers waiting? */
303       else if (wq_nr_sleepers (rw->readers_wq) > 0)
304         wq_wake_up_one (rw->readers_wq);
305     }
306 }