1 /* Multiple reader / single writer locks for pthrlib.
2 * by Richard W.M. Jones <rich@annexia.org>
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.
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.
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.
18 * $Id: pthr_rwlock.c,v 1.3 2002/12/01 14:29:30 rich Exp $
32 #include "pthr_pseudothread.h"
33 #include "pthr_wait_queue.h"
34 #include "pthr_rwlock.h"
36 /* I added this while I was trying to pin down a possible memory corruption
37 * problem. It can be disabled in normal operations.
39 /* #define RWLOCK_MEM_DEBUG 1 */
40 #define RWLOCK_MEM_DEBUG 0
43 #define RWLOCK_MEM_MAGIC 0x99775533
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.
55 wait_queue writers_wq; /* Writers wait on this queue. */
56 wait_queue readers_wq; /* Readers wait on this queue. */
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.
66 unsigned writers_have_priority:1;
69 static void _do_enter (rwlock);
70 static void _do_release (void *);
71 static void _delete_rwlock (void *);
76 rwlock rw = pmalloc (p, sizeof *rw);
79 rw->magic = RWLOCK_MEM_MAGIC;
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);
88 /* The purpose of this cleanup is just to check that the rwlock
89 * isn't released with threads in the critical section.
91 pool_register_cleanup_fn (p, _delete_rwlock, rw);
97 _delete_rwlock (void *vrw)
99 rwlock rw = (rwlock) vrw;
101 assert (rw->magic == RWLOCK_MEM_MAGIC);
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.]
113 rwlock_writers_have_priority (rwlock rw)
116 assert (rw->magic == RWLOCK_MEM_MAGIC);
118 rw->writers_have_priority = 1;
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
127 rwlock_readers_have_priority (rwlock rw)
130 assert (rw->magic == RWLOCK_MEM_MAGIC);
132 rw->writers_have_priority = 0;
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.
140 rwlock_try_enter_read (rwlock rw)
143 assert (rw->magic == RWLOCK_MEM_MAGIC);
147 (!rw->writers_have_priority ||
148 wq_nr_sleepers (rw->writers_wq) == 0))
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.
163 rwlock_try_enter_write (rwlock rw)
166 assert (rw->magic == RWLOCK_MEM_MAGIC);
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.
184 rwlock_enter_read (rwlock rw)
187 assert (rw->magic == RWLOCK_MEM_MAGIC);
190 while (rwlock_try_enter_read (rw) == 0)
191 wq_sleep_on (rw->readers_wq);
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.
199 rwlock_enter_write (rwlock rw)
202 assert (rw->magic == RWLOCK_MEM_MAGIC);
205 while (rwlock_try_enter_write (rw) == 0)
206 wq_sleep_on (rw->writers_wq);
209 /* Leave a critical section. */
211 rwlock_leave (rwlock rw)
216 assert (rw->magic == RWLOCK_MEM_MAGIC);
219 /* If this core dumps, it's probably because the pth didn't actually
222 if (!hash_get (rw->pools, current_pth, pool)) abort ();
224 /* Force _DO_RELEASE to run. */
234 /* This function registers a clean-up function which deals with the
235 * case when a thread exits early without releasing the lock.
238 _do_enter (rwlock rw)
240 struct cleanup_data *data;
244 assert (rw->magic == RWLOCK_MEM_MAGIC);
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.
252 pool = new_subpool (pth_get_pool (current_pth));
254 /* Save it in the hash. */
255 hash_insert (rw->pools, current_pth, pool);
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;
261 pool_register_cleanup_fn (pool, _do_release, data);
264 /* This function is called to do the actual work of releasing a lock. */
266 _do_release (void *vdata)
268 struct cleanup_data *data = (struct cleanup_data *)vdata;
269 pseudothread pth = data->pth;
270 rwlock rw = data->rw;
273 assert (rw->magic == RWLOCK_MEM_MAGIC);
278 /* Remove this pseudothread from rw->pools. */
279 if (!hash_erase (rw->pools, pth)) abort ();
281 if (rw->n > 0) /* Reader leaving critical section? */
286 /* Any writers waiting? */
287 if (wq_nr_sleepers (rw->writers_wq) > 0)
288 wq_wake_up_one (rw->writers_wq);
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);*/
295 else /* Writer leaving critical section? */
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);