strongswan/src/libstrongswan/threading/rwlock.c

500 lines
12 KiB
C

/*
* Copyright (C) 2008-2012 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* HSR Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#define _GNU_SOURCE
#include <pthread.h>
#include <library.h>
#include <utils/debug.h>
#include "rwlock.h"
#include "rwlock_condvar.h"
#include "thread.h"
#include "condvar.h"
#include "mutex.h"
#include "lock_profiler.h"
#ifdef __APPLE__
/* while pthread_rwlock_rdlock(3) says that it supports multiple read locks,
* this does not seem to be true. After releasing a recursive rdlock,
* a subsequent wrlock fails... */
# undef HAVE_PTHREAD_RWLOCK_INIT
#endif
typedef struct private_rwlock_t private_rwlock_t;
typedef struct private_rwlock_condvar_t private_rwlock_condvar_t;
/**
* private data of rwlock
*/
struct private_rwlock_t {
/**
* public functions
*/
rwlock_t public;
#ifdef HAVE_PTHREAD_RWLOCK_INIT
/**
* wrapped pthread rwlock
*/
pthread_rwlock_t rwlock;
#else
/**
* mutex to emulate a native rwlock
*/
mutex_t *mutex;
/**
* condvar to handle writers
*/
condvar_t *writers;
/**
* condvar to handle readers
*/
condvar_t *readers;
/**
* number of waiting writers
*/
u_int waiting_writers;
/**
* number of readers holding the lock
*/
u_int reader_count;
/**
* TRUE, if a writer is holding the lock currently
*/
bool writer;
#endif /* HAVE_PTHREAD_RWLOCK_INIT */
/**
* profiling info, if enabled
*/
lock_profile_t profile;
};
/**
* private data of condvar
*/
struct private_rwlock_condvar_t {
/**
* public interface
*/
rwlock_condvar_t public;
/**
* mutex used to implement rwlock condvar
*/
mutex_t *mutex;
/**
* regular condvar to implement rwlock condvar
*/
condvar_t *condvar;
};
#ifdef HAVE_PTHREAD_RWLOCK_INIT
METHOD(rwlock_t, read_lock, void,
private_rwlock_t *this)
{
int err;
profiler_start(&this->profile);
err = pthread_rwlock_rdlock(&this->rwlock);
if (err != 0)
{
DBG1(DBG_LIB, "!!! RWLOCK READ LOCK ERROR: %s !!!", strerror(err));
}
profiler_end(&this->profile);
}
METHOD(rwlock_t, write_lock, void,
private_rwlock_t *this)
{
int err;
profiler_start(&this->profile);
err = pthread_rwlock_wrlock(&this->rwlock);
if (err != 0)
{
DBG1(DBG_LIB, "!!! RWLOCK WRITE LOCK ERROR: %s !!!", strerror(err));
}
profiler_end(&this->profile);
}
METHOD(rwlock_t, try_write_lock, bool,
private_rwlock_t *this)
{
return pthread_rwlock_trywrlock(&this->rwlock) == 0;
}
METHOD(rwlock_t, unlock, void,
private_rwlock_t *this)
{
int err;
err = pthread_rwlock_unlock(&this->rwlock);
if (err != 0)
{
DBG1(DBG_LIB, "!!! RWLOCK UNLOCK ERROR: %s !!!", strerror(err));
}
}
METHOD(rwlock_t, destroy, void,
private_rwlock_t *this)
{
pthread_rwlock_destroy(&this->rwlock);
profiler_cleanup(&this->profile);
free(this);
}
/*
* see header file
*/
rwlock_t *rwlock_create(rwlock_type_t type)
{
switch (type)
{
case RWLOCK_TYPE_DEFAULT:
default:
{
private_rwlock_t *this;
INIT(this,
.public = {
.read_lock = _read_lock,
.write_lock = _write_lock,
.try_write_lock = _try_write_lock,
.unlock = _unlock,
.destroy = _destroy,
}
);
pthread_rwlock_init(&this->rwlock, NULL);
profiler_init(&this->profile);
return &this->public;
}
}
}
#else /* HAVE_PTHREAD_RWLOCK_INIT */
/**
* This implementation of the rwlock_t interface uses mutex_t and condvar_t
* primitives, if the pthread_rwlock_* group of functions is not available or
* don't allow recursive locking for readers.
*
* The following constraints are enforced:
* - Multiple readers can hold the lock at the same time.
* - Only a single writer can hold the lock at any given time.
* - A writer must block until all readers have released the lock before
* obtaining the lock exclusively.
* - Readers that don't hold any read lock and arrive while a writer is
* waiting to acquire the lock will block until after the writer has
* obtained and released the lock.
* These constraints allow for read sharing, prevent write sharing, prevent
* read-write sharing and (largely) prevent starvation of writers by a steady
* stream of incoming readers. Reader starvation is not prevented (this could
* happen if there are more writers than readers).
*
* The implementation supports recursive locking of the read lock but not of
* the write lock. Readers must not acquire the lock exclusively at the same
* time and vice-versa (this is not checked or enforced so behave yourself to
* prevent deadlocks).
*
* Since writers are preferred a thread currently holding the read lock that
* tries to acquire the read lock recursively while a writer is waiting would
* result in a deadlock. In order to avoid having to use a thread-specific
* value for each rwlock_t (or a list of threads) to keep track if a thread
* already acquired the read lock we use a single thread-specific value for all
* rwlock_t objects that keeps track of how many read locks a thread currently
* holds. Preferring readers that already hold ANY read locks prevents this
* deadlock while it still largely avoids writer starvation (for locks that can
* only be acquired while holding another read lock this will obviously not
* work).
*/
/**
* Keep track of how many read locks a thread holds.
*/
static pthread_key_t is_reader;
/**
* Only initialize the read lock counter once.
*/
static pthread_once_t is_reader_initialized = PTHREAD_ONCE_INIT;
/**
* Initialize the read lock counter.
*/
static void initialize_is_reader()
{
pthread_key_create(&is_reader, NULL);
}
METHOD(rwlock_t, read_lock, void,
private_rwlock_t *this)
{
uintptr_t reading;
bool old;
reading = (uintptr_t)pthread_getspecific(is_reader);
profiler_start(&this->profile);
this->mutex->lock(this->mutex);
if (!this->writer && reading > 0)
{
/* directly allow threads that hold ANY read locks, to avoid a deadlock
* caused by preferring writers in the loop below */
}
else
{
old = thread_cancelability(FALSE);
while (this->writer || this->waiting_writers)
{
this->readers->wait(this->readers, this->mutex);
}
thread_cancelability(old);
}
this->reader_count++;
profiler_end(&this->profile);
this->mutex->unlock(this->mutex);
pthread_setspecific(is_reader, (void*)(reading + 1));
}
METHOD(rwlock_t, write_lock, void,
private_rwlock_t *this)
{
bool old;
profiler_start(&this->profile);
this->mutex->lock(this->mutex);
this->waiting_writers++;
old = thread_cancelability(FALSE);
while (this->writer || this->reader_count)
{
this->writers->wait(this->writers, this->mutex);
}
thread_cancelability(old);
this->waiting_writers--;
this->writer = TRUE;
profiler_end(&this->profile);
this->mutex->unlock(this->mutex);
}
METHOD(rwlock_t, try_write_lock, bool,
private_rwlock_t *this)
{
bool res = FALSE;
this->mutex->lock(this->mutex);
if (!this->writer && !this->reader_count)
{
res = this->writer = TRUE;
}
this->mutex->unlock(this->mutex);
return res;
}
METHOD(rwlock_t, unlock, void,
private_rwlock_t *this)
{
this->mutex->lock(this->mutex);
if (this->writer)
{
this->writer = FALSE;
}
else
{
uintptr_t reading;
this->reader_count--;
reading = (uintptr_t)pthread_getspecific(is_reader);
pthread_setspecific(is_reader, (void*)(reading - 1));
}
if (!this->reader_count)
{
if (this->waiting_writers)
{
this->writers->signal(this->writers);
}
else
{
this->readers->broadcast(this->readers);
}
}
this->mutex->unlock(this->mutex);
}
METHOD(rwlock_t, destroy, void,
private_rwlock_t *this)
{
this->mutex->destroy(this->mutex);
this->writers->destroy(this->writers);
this->readers->destroy(this->readers);
profiler_cleanup(&this->profile);
free(this);
}
/*
* see header file
*/
rwlock_t *rwlock_create(rwlock_type_t type)
{
pthread_once(&is_reader_initialized, initialize_is_reader);
switch (type)
{
case RWLOCK_TYPE_DEFAULT:
default:
{
private_rwlock_t *this;
INIT(this,
.public = {
.read_lock = _read_lock,
.write_lock = _write_lock,
.try_write_lock = _try_write_lock,
.unlock = _unlock,
.destroy = _destroy,
},
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.writers = condvar_create(CONDVAR_TYPE_DEFAULT),
.readers = condvar_create(CONDVAR_TYPE_DEFAULT),
);
profiler_init(&this->profile);
return &this->public;
}
}
}
#endif /* HAVE_PTHREAD_RWLOCK_INIT */
METHOD(rwlock_condvar_t, wait_, void,
private_rwlock_condvar_t *this, rwlock_t *lock)
{
/* at this point we have the write lock locked, to make signals more
* predictable we try to prevent other threads from signaling by acquiring
* the mutex while we still hold the write lock (this assumes they will
* hold the write lock themselves when signaling, which is not mandatory) */
this->mutex->lock(this->mutex);
/* unlock the rwlock and wait for a signal */
lock->unlock(lock);
/* if the calling thread enabled thread cancelability we want to replicate
* the behavior of the regular condvar, i.e. the lock will be held again
* before executing cleanup functions registered by the calling thread */
thread_cleanup_push((thread_cleanup_t)lock->write_lock, lock);
thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex);
this->condvar->wait(this->condvar, this->mutex);
/* we release the mutex to allow other threads into the condvar (might even
* be required so we can acquire the lock again below) */
thread_cleanup_pop(TRUE);
/* finally we reacquire the lock we held previously */
thread_cleanup_pop(TRUE);
}
METHOD(rwlock_condvar_t, timed_wait_abs, bool,
private_rwlock_condvar_t *this, rwlock_t *lock, timeval_t time)
{
bool timed_out;
/* see wait() above for details on what is going on here */
this->mutex->lock(this->mutex);
lock->unlock(lock);
thread_cleanup_push((thread_cleanup_t)lock->write_lock, lock);
thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex);
timed_out = this->condvar->timed_wait_abs(this->condvar, this->mutex, time);
thread_cleanup_pop(TRUE);
thread_cleanup_pop(TRUE);
return timed_out;
}
METHOD(rwlock_condvar_t, timed_wait, bool,
private_rwlock_condvar_t *this, rwlock_t *lock, u_int timeout)
{
timeval_t tv;
u_int s, ms;
time_monotonic(&tv);
s = timeout / 1000;
ms = timeout % 1000;
tv.tv_sec += s;
timeval_add_ms(&tv, ms);
return timed_wait_abs(this, lock, tv);
}
METHOD(rwlock_condvar_t, signal_, void,
private_rwlock_condvar_t *this)
{
this->mutex->lock(this->mutex);
this->condvar->signal(this->condvar);
this->mutex->unlock(this->mutex);
}
METHOD(rwlock_condvar_t, broadcast, void,
private_rwlock_condvar_t *this)
{
this->mutex->lock(this->mutex);
this->condvar->broadcast(this->condvar);
this->mutex->unlock(this->mutex);
}
METHOD(rwlock_condvar_t, condvar_destroy, void,
private_rwlock_condvar_t *this)
{
this->condvar->destroy(this->condvar);
this->mutex->destroy(this->mutex);
free(this);
}
/*
* see header file
*/
rwlock_condvar_t *rwlock_condvar_create()
{
private_rwlock_condvar_t *this;
INIT(this,
.public = {
.wait = _wait_,
.timed_wait = _timed_wait,
.timed_wait_abs = _timed_wait_abs,
.signal = _signal_,
.broadcast = _broadcast,
.destroy = _condvar_destroy,
},
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
);
return &this->public;
}