Properly handle thread cancelation in rwlock_condvar_t
[strongswan.git] / src / libstrongswan / threading / rwlock.c
1 /*
2 * Copyright (C) 2008-2012 Tobias Brunner
3 * Copyright (C) 2008 Martin Willi
4 * Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #define _GNU_SOURCE
18 #include <pthread.h>
19
20 #include <library.h>
21 #include <debug.h>
22
23 #include "rwlock.h"
24 #include "rwlock_condvar.h"
25 #include "thread.h"
26 #include "condvar.h"
27 #include "mutex.h"
28 #include "lock_profiler.h"
29
30 typedef struct private_rwlock_t private_rwlock_t;
31 typedef struct private_rwlock_condvar_t private_rwlock_condvar_t;
32
33 /**
34 * private data of rwlock
35 */
36 struct private_rwlock_t {
37
38 /**
39 * public functions
40 */
41 rwlock_t public;
42
43 #ifdef HAVE_PTHREAD_RWLOCK_INIT
44
45 /**
46 * wrapped pthread rwlock
47 */
48 pthread_rwlock_t rwlock;
49
50 #else
51
52 /**
53 * mutex to emulate a native rwlock
54 */
55 mutex_t *mutex;
56
57 /**
58 * condvar to handle writers
59 */
60 condvar_t *writers;
61
62 /**
63 * condvar to handle readers
64 */
65 condvar_t *readers;
66
67 /**
68 * number of waiting writers
69 */
70 u_int waiting_writers;
71
72 /**
73 * number of readers holding the lock
74 */
75 u_int reader_count;
76
77 /**
78 * TRUE, if a writer is holding the lock currently
79 */
80 bool writer;
81
82 #endif /* HAVE_PTHREAD_RWLOCK_INIT */
83
84 /**
85 * profiling info, if enabled
86 */
87 lock_profile_t profile;
88 };
89
90 /**
91 * private data of condvar
92 */
93 struct private_rwlock_condvar_t {
94
95 /**
96 * public interface
97 */
98 rwlock_condvar_t public;
99
100 /**
101 * mutex used to implement rwlock condvar
102 */
103 mutex_t *mutex;
104
105 /**
106 * regular condvar to implement rwlock condvar
107 */
108 condvar_t *condvar;
109 };
110
111
112 #ifdef HAVE_PTHREAD_RWLOCK_INIT
113
114 METHOD(rwlock_t, read_lock, void,
115 private_rwlock_t *this)
116 {
117 int err;
118
119 profiler_start(&this->profile);
120 err = pthread_rwlock_rdlock(&this->rwlock);
121 if (err != 0)
122 {
123 DBG1(DBG_LIB, "!!! RWLOCK READ LOCK ERROR: %s !!!", strerror(err));
124 }
125 profiler_end(&this->profile);
126 }
127
128 METHOD(rwlock_t, write_lock, void,
129 private_rwlock_t *this)
130 {
131 int err;
132
133 profiler_start(&this->profile);
134 err = pthread_rwlock_wrlock(&this->rwlock);
135 if (err != 0)
136 {
137 DBG1(DBG_LIB, "!!! RWLOCK WRITE LOCK ERROR: %s !!!", strerror(err));
138 }
139 profiler_end(&this->profile);
140 }
141
142 METHOD(rwlock_t, try_write_lock, bool,
143 private_rwlock_t *this)
144 {
145 return pthread_rwlock_trywrlock(&this->rwlock) == 0;
146 }
147
148 METHOD(rwlock_t, unlock, void,
149 private_rwlock_t *this)
150 {
151 int err;
152
153 err = pthread_rwlock_unlock(&this->rwlock);
154 if (err != 0)
155 {
156 DBG1(DBG_LIB, "!!! RWLOCK UNLOCK ERROR: %s !!!", strerror(err));
157 }
158 }
159
160 METHOD(rwlock_t, destroy, void,
161 private_rwlock_t *this)
162 {
163 pthread_rwlock_destroy(&this->rwlock);
164 profiler_cleanup(&this->profile);
165 free(this);
166 }
167
168 /*
169 * see header file
170 */
171 rwlock_t *rwlock_create(rwlock_type_t type)
172 {
173 switch (type)
174 {
175 case RWLOCK_TYPE_DEFAULT:
176 default:
177 {
178 private_rwlock_t *this;
179
180 INIT(this,
181 .public = {
182 .read_lock = _read_lock,
183 .write_lock = _write_lock,
184 .try_write_lock = _try_write_lock,
185 .unlock = _unlock,
186 .destroy = _destroy,
187 }
188 );
189
190 pthread_rwlock_init(&this->rwlock, NULL);
191 profiler_init(&this->profile);
192
193 return &this->public;
194 }
195 }
196 }
197
198 #else /* HAVE_PTHREAD_RWLOCK_INIT */
199
200 /**
201 * This implementation of the rwlock_t interface uses mutex_t and condvar_t
202 * primitives, if the pthread_rwlock_* group of functions is not available or
203 * don't allow recursive locking for readers.
204 *
205 * The following constraints are enforced:
206 * - Multiple readers can hold the lock at the same time.
207 * - Only a single writer can hold the lock at any given time.
208 * - A writer must block until all readers have released the lock before
209 * obtaining the lock exclusively.
210 * - Readers that don't hold any read lock and arrive while a writer is
211 * waiting to acquire the lock will block until after the writer has
212 * obtained and released the lock.
213 * These constraints allow for read sharing, prevent write sharing, prevent
214 * read-write sharing and (largely) prevent starvation of writers by a steady
215 * stream of incoming readers. Reader starvation is not prevented (this could
216 * happen if there are more writers than readers).
217 *
218 * The implementation supports recursive locking of the read lock but not of
219 * the write lock. Readers must not acquire the lock exclusively at the same
220 * time and vice-versa (this is not checked or enforced so behave yourself to
221 * prevent deadlocks).
222 *
223 * Since writers are preferred a thread currently holding the read lock that
224 * tries to acquire the read lock recursively while a writer is waiting would
225 * result in a deadlock. In order to avoid having to use a thread-specific
226 * value for each rwlock_t (or a list of threads) to keep track if a thread
227 * already acquired the read lock we use a single thread-specific value for all
228 * rwlock_t objects that keeps track of how many read locks a thread currently
229 * holds. Preferring readers that already hold ANY read locks prevents this
230 * deadlock while it still largely avoids writer starvation (for locks that can
231 * only be acquired while holding another read lock this will obviously not
232 * work).
233 */
234
235 /**
236 * Keep track of how many read locks a thread holds.
237 */
238 static pthread_key_t is_reader;
239
240 /**
241 * Only initialize the read lock counter once.
242 */
243 static pthread_once_t is_reader_initialized = PTHREAD_ONCE_INIT;
244
245 /**
246 * Initialize the read lock counter.
247 */
248 static void initialize_is_reader()
249 {
250 pthread_key_create(&is_reader, NULL);
251 }
252
253 METHOD(rwlock_t, read_lock, void,
254 private_rwlock_t *this)
255 {
256 uintptr_t reading;
257
258 reading = (uintptr_t)pthread_getspecific(is_reader);
259 profiler_start(&this->profile);
260 this->mutex->lock(this->mutex);
261 if (!this->writer && reading > 0)
262 {
263 /* directly allow threads that hold ANY read locks, to avoid a deadlock
264 * caused by preferring writers in the loop below */
265 }
266 else
267 {
268 while (this->writer || this->waiting_writers)
269 {
270 this->readers->wait(this->readers, this->mutex);
271 }
272 }
273 this->reader_count++;
274 profiler_end(&this->profile);
275 this->mutex->unlock(this->mutex);
276 pthread_setspecific(is_reader, (void*)(reading + 1));
277 }
278
279 METHOD(rwlock_t, write_lock, void,
280 private_rwlock_t *this)
281 {
282 profiler_start(&this->profile);
283 this->mutex->lock(this->mutex);
284 this->waiting_writers++;
285 while (this->writer || this->reader_count)
286 {
287 this->writers->wait(this->writers, this->mutex);
288 }
289 this->waiting_writers--;
290 this->writer = TRUE;
291 profiler_end(&this->profile);
292 this->mutex->unlock(this->mutex);
293 }
294
295 METHOD(rwlock_t, try_write_lock, bool,
296 private_rwlock_t *this)
297 {
298 bool res = FALSE;
299 this->mutex->lock(this->mutex);
300 if (!this->writer && !this->reader_count)
301 {
302 res = this->writer = TRUE;
303 }
304 this->mutex->unlock(this->mutex);
305 return res;
306 }
307
308 METHOD(rwlock_t, unlock, void,
309 private_rwlock_t *this)
310 {
311 this->mutex->lock(this->mutex);
312 if (this->writer)
313 {
314 this->writer = FALSE;
315 }
316 else
317 {
318 uintptr_t reading;
319
320 this->reader_count--;
321 reading = (uintptr_t)pthread_getspecific(is_reader);
322 pthread_setspecific(is_reader, (void*)(reading - 1));
323 }
324 if (!this->reader_count)
325 {
326 if (this->waiting_writers)
327 {
328 this->writers->signal(this->writers);
329 }
330 else
331 {
332 this->readers->broadcast(this->readers);
333 }
334 }
335 this->mutex->unlock(this->mutex);
336 }
337
338 METHOD(rwlock_t, destroy, void,
339 private_rwlock_t *this)
340 {
341 this->mutex->destroy(this->mutex);
342 this->writers->destroy(this->writers);
343 this->readers->destroy(this->readers);
344 profiler_cleanup(&this->profile);
345 free(this);
346 }
347
348 /*
349 * see header file
350 */
351 rwlock_t *rwlock_create(rwlock_type_t type)
352 {
353 pthread_once(&is_reader_initialized, initialize_is_reader);
354
355 switch (type)
356 {
357 case RWLOCK_TYPE_DEFAULT:
358 default:
359 {
360 private_rwlock_t *this;
361
362 INIT(this,
363 .public = {
364 .read_lock = _read_lock,
365 .write_lock = _write_lock,
366 .try_write_lock = _try_write_lock,
367 .unlock = _unlock,
368 .destroy = _destroy,
369 },
370 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
371 .writers = condvar_create(CONDVAR_TYPE_DEFAULT),
372 .readers = condvar_create(CONDVAR_TYPE_DEFAULT),
373 );
374
375 profiler_init(&this->profile);
376
377 return &this->public;
378 }
379 }
380 }
381
382 #endif /* HAVE_PTHREAD_RWLOCK_INIT */
383
384
385 METHOD(rwlock_condvar_t, wait_, void,
386 private_rwlock_condvar_t *this, rwlock_t *lock)
387 {
388 /* at this point we have the write lock locked, to make signals more
389 * predictable we try to prevent other threads from signaling by acquiring
390 * the mutex while we still hold the write lock (this assumes they will
391 * hold the write lock themselves when signaling, which is not mandatory) */
392 this->mutex->lock(this->mutex);
393 /* unlock the rwlock and wait for a signal */
394 lock->unlock(lock);
395 /* if the calling thread enabled thread cancelability we want to replicate
396 * the behavior of the regular condvar, i.e. the lock will be held again
397 * before executing cleanup functions registered by the calling thread */
398 thread_cleanup_push((thread_cleanup_t)lock->write_lock, lock);
399 thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex);
400 this->condvar->wait(this->condvar, this->mutex);
401 /* we release the mutex to allow other threads into the condvar (might even
402 * be required so we can acquire the lock again below) */
403 thread_cleanup_pop(TRUE);
404 /* finally we reacquire the lock we held previously */
405 thread_cleanup_pop(TRUE);
406 }
407
408 METHOD(rwlock_condvar_t, timed_wait_abs, bool,
409 private_rwlock_condvar_t *this, rwlock_t *lock, timeval_t time)
410 {
411 bool timed_out;
412
413 /* see wait() above for details on what is going on here */
414 this->mutex->lock(this->mutex);
415 lock->unlock(lock);
416 thread_cleanup_push((thread_cleanup_t)lock->write_lock, lock);
417 thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex);
418 timed_out = this->condvar->timed_wait_abs(this->condvar, this->mutex, time);
419 thread_cleanup_pop(TRUE);
420 thread_cleanup_pop(!timed_out);
421 return timed_out;
422 }
423
424 METHOD(rwlock_condvar_t, timed_wait, bool,
425 private_rwlock_condvar_t *this, rwlock_t *lock, u_int timeout)
426 {
427 timeval_t tv;
428 u_int s, ms;
429
430 time_monotonic(&tv);
431
432 s = timeout / 1000;
433 ms = timeout % 1000;
434
435 tv.tv_sec += s;
436 tv.tv_usec += ms * 1000;
437
438 if (tv.tv_usec > 1000000 /* 1s */)
439 {
440 tv.tv_usec -= 1000000;
441 tv.tv_sec++;
442 }
443 return timed_wait_abs(this, lock, tv);
444 }
445
446 METHOD(rwlock_condvar_t, signal_, void,
447 private_rwlock_condvar_t *this)
448 {
449 this->mutex->lock(this->mutex);
450 this->condvar->signal(this->condvar);
451 this->mutex->unlock(this->mutex);
452 }
453
454 METHOD(rwlock_condvar_t, broadcast, void,
455 private_rwlock_condvar_t *this)
456 {
457 this->mutex->lock(this->mutex);
458 this->condvar->broadcast(this->condvar);
459 this->mutex->unlock(this->mutex);
460 }
461
462 METHOD(rwlock_condvar_t, condvar_destroy, void,
463 private_rwlock_condvar_t *this)
464 {
465 this->condvar->destroy(this->condvar);
466 this->mutex->destroy(this->mutex);
467 free(this);
468 }
469
470 /*
471 * see header file
472 */
473 rwlock_condvar_t *rwlock_condvar_create()
474 {
475 private_rwlock_condvar_t *this;
476
477 INIT(this,
478 .public = {
479 .wait = _wait_,
480 .timed_wait = _timed_wait,
481 .timed_wait_abs = _timed_wait_abs,
482 .signal = _signal_,
483 .broadcast = _broadcast,
484 .destroy = _condvar_destroy,
485 },
486 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
487 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
488 );
489 return &this->public;
490 }