--- /dev/null
+/**
+ * testcode/checklocks.c - wrapper on locks that checks access.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <signal.h>
+#include "util/locks.h" /* include before checklocks.h */
+#include "testcode/checklocks.h"
+
+/**
+ * \file
+ * Locks that are checked.
+ *
+ * Ugly hack: uses the fact that workers are passed to thread_create to make
+ * the thread numbers here the same as those used for logging which is nice.
+ *
+ * Todo: - check global ordering of instances of locks.
+ * - refcount statistics.
+ * - debug status print, of thread lock stacks, and current waiting.
+ */
+#ifdef USE_THREAD_DEBUG
+
+/** if key has been created */
+static int key_created = 0;
+/** we hide the thread debug info with this key. */
+static ub_thread_key_t thr_debug_key;
+/** the list of threads, so all threads can be examined. NULL at start. */
+static struct thr_check* thread_infos[THRDEBUG_MAX_THREADS];
+
+/** print pretty lock error and exit */
+static void lock_error(struct checked_lock* lock,
+ const char* func, const char* file, int line, const char* err)
+{
+ log_err("lock error (description follows)");
+ log_err("Created at %s %s %d", lock->create_func, lock->create_file, lock->create_line);
+ log_err("Previously %s %s %d", lock->holder_func, lock->holder_file, lock->holder_line);
+ log_err("At %s %s %d", func, file, line);
+ log_err("Error for %s lock: %s",
+ (lock->type==check_lock_mutex)?"mutex": (
+ (lock->type==check_lock_spinlock)?"spinlock": "rwlock"), err);
+ fatal_exit("bailing out");
+}
+
+/** obtain lock on debug lock structure. This could be a deadlock.
+ * (could it?) Anyway, check with timeouts.
+ * @param lock: on what to acquire lock.
+ * @param func: user level caller identification.
+ * @param file: user level caller identification.
+ * @param line: user level caller identification.
+ */
+static void
+acquire_locklock(struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ struct timespec to;
+ int err;
+ int contend = 0;
+ /* first try; inc contention counter if not immediately */
+ if((err = pthread_mutex_trylock(&lock->lock))) {
+ if(err==EBUSY)
+ contend++;
+ else fatal_exit("error in mutex_trylock: %s", strerror(err));
+ }
+ if(!err)
+ return; /* immediate success */
+ to.tv_sec = time(NULL) + CHECK_LOCK_TIMEOUT;
+ to.tv_nsec = 0;
+ err = pthread_mutex_timedlock(&lock->lock, &to);
+ if(err) {
+ log_err("in acquiring locklock: %s", strerror(err));
+ lock_error(lock, func, file, line, "acquire locklock");
+ }
+ lock->contention_count += contend;
+}
+
+/** add protected region */
+void
+lock_protect(struct checked_lock* lock, void* area, size_t size)
+{
+ struct protected_area* e = (struct protected_area*)calloc(1,
+ sizeof(struct protected_area));
+ if(!e)
+ fatal_exit("lock_protect: out of memory");
+ e->region = area;
+ e->size = size;
+ e->hold = calloc(1, size);
+ if(!e->hold)
+ fatal_exit("lock_protect: out of memory");
+ memcpy(e->hold, e->region, e->size);
+
+ acquire_locklock(lock, __func__, __FILE__, __LINE__);
+ e->next = lock->prot;
+ lock->prot = e;
+ LOCKRET(pthread_mutex_unlock(&lock->lock));
+}
+
+/**
+ * Check protected memory region. Memory compare. Exit on error.
+ * @param lock: which lock to check.
+ * @param func: location we are now (when failure is detected).
+ * @param file: location we are now (when failure is detected).
+ * @param line: location we are now (when failure is detected).
+ */
+static void
+prot_check(struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ struct protected_area* p = lock->prot;
+ while(p) {
+ if(memcmp(p->hold, p->region, p->size) != 0) {
+ lock_error(lock, func, file, line,
+ "protected area modified");
+ }
+ p = p->next;
+ }
+}
+
+/** Copy protected memory region. */
+static void
+prot_store(struct checked_lock* lock)
+{
+ struct protected_area* p = lock->prot;
+ while(p) {
+ memcpy(p->hold, p->region, p->size);
+ p = p->next;
+ }
+}
+
+
+/** alloc struct, init lock empty */
+void
+checklock_init(enum check_lock_type type, struct checked_lock** lock,
+ const char* func, const char* file, int line)
+{
+ struct checked_lock* e = (struct checked_lock*)calloc(1,
+ sizeof(struct checked_lock));
+ if(!e)
+ fatal_exit("%s %s %d: out of memory", func, file, line);
+ *lock = e;
+ e->type = type;
+ e->create_func = func;
+ e->create_file = file;
+ e->create_line = line;
+ LOCKRET(pthread_mutex_init(&e->lock, NULL));
+ switch(e->type) {
+ case check_lock_mutex:
+ LOCKRET(pthread_mutex_init(&e->mutex, NULL));
+ break;
+ case check_lock_spinlock:
+ LOCKRET(pthread_spin_init(&e->spinlock, PTHREAD_PROCESS_PRIVATE));
+ break;
+ case check_lock_rwlock:
+ LOCKRET(pthread_rwlock_init(&e->rwlock, NULL));
+ break;
+ default:
+ log_assert(0);
+ }
+}
+
+/** delete prot items */
+static void prot_delete(struct checked_lock* lock)
+{
+ struct protected_area* p=lock->prot, *np;
+ while(p) {
+ np = p->next;
+ free(p->hold);
+ free(p);
+ p = np;
+ }
+}
+
+/** check if type is OK for the lock given */
+static void
+checktype(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ if(type != lock->type) {
+ lock_error(lock, func, file, line, "wrong lock type");
+ }
+}
+
+/** check if OK, free struct */
+void
+checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
+ const char* func, const char* file, int line)
+{
+ const size_t contention_interest = 10;
+ struct checked_lock* e;
+ if(!lock)
+ return;
+ e = *lock;
+ if(!e)
+ return;
+ *lock = NULL; /* use after free will fail */
+ checktype(type, e, func, file, line);
+
+ /* check if delete is OK */
+ acquire_locklock(e, func, file, line);
+ if(e->hold_count != 0)
+ lock_error(e, func, file, line, "delete while locked.");
+ if(e->wait_count != 0)
+ lock_error(e, func, file, line, "delete while waited on.");
+ prot_check(e, func, file, line);
+ LOCKRET(pthread_mutex_unlock(&e->lock));
+
+ /* contention */
+ if(e->contention_count > contention_interest) {
+ log_info("lock created %s %s %d has contention %u",
+ e->create_func, e->create_file, e->create_line,
+ (unsigned int)e->contention_count);
+ }
+
+ /* delete it */
+ LOCKRET(pthread_mutex_destroy(&e->lock));
+ prot_delete(e);
+ /* since nobody holds the lock - see check above, no need to unlink */
+ switch(e->type) {
+ case check_lock_mutex:
+ LOCKRET(pthread_mutex_destroy(&e->mutex));
+ break;
+ case check_lock_spinlock:
+ LOCKRET(pthread_spin_destroy(&e->spinlock));
+ break;
+ case check_lock_rwlock:
+ LOCKRET(pthread_rwlock_destroy(&e->rwlock));
+ break;
+ default:
+ log_assert(0);
+ }
+ memset(e, 0, sizeof(*lock));
+ free(e);
+}
+
+/** finish acquiring lock, shared between _(rd|wr||)lock() routines. */
+static void
+finish_acquire_lock(struct thr_check* thr, struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ thr->waiting = NULL;
+ lock->wait_count --;
+ lock->holder = thr;
+ lock->hold_count ++;
+ lock->holder_func = func;
+ lock->holder_file = file;
+ lock->holder_line = line;
+
+ /* insert in thread lock list, as first */
+ lock->prev_held_lock[thr->num] = NULL;
+ lock->next_held_lock[thr->num] = thr->holding_first;
+ if(thr->holding_first)
+ /* no need to lock it, since this thread already holds the
+ * lock (since it is on this list) and we only edit thr->num
+ * member in array. So it is safe. */
+ thr->holding_first->prev_held_lock[thr->num] = lock;
+ else thr->holding_last = lock;
+ thr->holding_first = lock;
+}
+
+/**
+ * Locking routine.
+ * @param type: as passed by user.
+ * @param lock: as passed by user.
+ * @param func: caller location.
+ * @param file: caller location.
+ * @param line: caller location.
+ * @param tryfunc: the pthread_mutex_trylock or similar function.
+ * @param timedfunc: the pthread_mutex_timedlock or similar function.
+ * Uses absolute timeout value.
+ * @param arg: what to pass to tryfunc and timedlock.
+ * @param exclusive: if lock must be exlusive (only one allowed).
+ */
+static void
+checklock_lockit(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line,
+ int (*tryfunc)(void*), int (*timedfunc)(void*, struct timespec*),
+ void* arg, int exclusive)
+{
+ int err;
+ int contend = 0;
+ struct thr_check *thr = (struct thr_check*)pthread_getspecific(
+ thr_debug_key);
+ checktype(type, lock, func, file, line);
+ if(!thr) lock_error(lock, func, file, line, "no thread info");
+
+ acquire_locklock(lock, func, file, line);
+ lock->wait_count ++;
+ thr->waiting = lock;
+ if(exclusive && lock->hold_count > 0 && lock->holder == thr)
+ lock_error(lock, func, file, line, "thread already owns lock");
+ LOCKRET(pthread_mutex_unlock(&lock->lock));
+
+ /* first try; if busy increase contention counter */
+ if((err=tryfunc(arg))) {
+ struct timespec to;
+ if(err != EBUSY) log_err("trylock: %s", strerror(err));
+ to.tv_sec = time(NULL) + CHECK_LOCK_TIMEOUT;
+ to.tv_nsec = 0;
+ if((err=timedfunc(arg, &to))) {
+ if(err == ETIMEDOUT)
+ lock_error(lock, func, file, line,
+ "timeout, deadlock?");
+ log_err("timedlock: %s", strerror(err));
+ }
+ contend ++;
+ }
+ /* got the lock */
+
+ acquire_locklock(lock, func, file, line);
+ lock->contention_count += contend;
+ if(exclusive && lock->hold_count > 0)
+ lock_error(lock, func, file, line, "got nonexclusive lock");
+ /* check the memory areas for unauthorized changes,
+ * between last unlock time and current lock time.
+ * we check while holding the lock (threadsafe).
+ */
+ prot_check(lock, func, file, line);
+ finish_acquire_lock(thr, lock, func, file, line);
+ LOCKRET(pthread_mutex_unlock(&lock->lock));
+}
+
+/** helper for rdlock: try */
+static int try_rd(void* arg)
+{ return pthread_rwlock_tryrdlock((pthread_rwlock_t*)arg); }
+/** helper for rdlock: timed */
+static int timed_rd(void* arg, struct timespec* to)
+{ return pthread_rwlock_timedrdlock((pthread_rwlock_t*)arg, to); }
+
+/** check if OK, lock */
+void
+checklock_rdlock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+
+ log_assert(type == check_lock_rwlock);
+ checklock_lockit(type, lock, func, file, line,
+ try_rd, timed_rd, &lock->rwlock, 0);
+}
+
+/** helper for wrlock: try */
+static int try_wr(void* arg)
+{ return pthread_rwlock_trywrlock((pthread_rwlock_t*)arg); }
+/** helper for wrlock: timed */
+static int timed_wr(void* arg, struct timespec* to)
+{ return pthread_rwlock_timedwrlock((pthread_rwlock_t*)arg, to); }
+
+/** check if OK, lock */
+void
+checklock_wrlock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ log_assert(type == check_lock_rwlock);
+ checklock_lockit(type, lock, func, file, line,
+ try_wr, timed_wr, &lock->rwlock, 0);
+}
+
+/** helper for lock mutex: try */
+static int try_mutex(void* arg)
+{ return pthread_mutex_trylock((pthread_mutex_t*)arg); }
+/** helper for lock mutex: timed */
+static int timed_mutex(void* arg, struct timespec* to)
+{ return pthread_mutex_timedlock((pthread_mutex_t*)arg, to); }
+
+/** helper for lock spinlock: try */
+static int try_spinlock(void* arg)
+{ return pthread_spin_trylock((pthread_spinlock_t*)arg); }
+/** helper for lock spinlock: timed */
+static int timed_spinlock(void* arg, struct timespec* to)
+{
+ int err;
+ /* spin for 5 seconds. (ouch for the CPU, but it beats forever) */
+ while( (err=try_spinlock(arg)) == EBUSY) {
+#ifndef S_SPLINT_S
+ if(time(NULL) >= to->tv_sec)
+ return ETIMEDOUT;
+#endif
+ }
+ return err;
+}
+
+/** check if OK, lock */
+void
+checklock_lock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ log_assert(type != check_lock_rwlock);
+ switch(type) {
+ case check_lock_mutex:
+ checklock_lockit(type, lock, func, file, line,
+ try_mutex, timed_mutex, &lock->mutex, 1);
+ break;
+ case check_lock_spinlock:
+ /* void* cast needed because 'volatile' on some OS */
+ checklock_lockit(type, lock, func, file, line,
+ try_spinlock, timed_spinlock,
+ (void*)&lock->spinlock, 1);
+ break;
+ default:
+ log_assert(0);
+ }
+}
+
+/** check if OK, unlock */
+void
+checklock_unlock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line)
+{
+ struct thr_check *thr = (struct thr_check*)pthread_getspecific(
+ thr_debug_key);
+ checktype(type, lock, func, file, line);
+ if(!thr) lock_error(lock, func, file, line, "no thread info");
+
+ acquire_locklock(lock, func, file, line);
+ /* was this thread even holding this lock? */
+ if(thr->holding_first != lock &&
+ lock->prev_held_lock[thr->num] == NULL) {
+ lock_error(lock, func, file, line, "unlock nonlocked lock");
+ }
+ if(lock->hold_count <= 0)
+ lock_error(lock, func, file, line, "too many unlocks");
+
+ /* store this point as last touched by */
+ lock->holder = thr;
+ lock->hold_count --;
+ lock->holder_func = func;
+ lock->holder_file = file;
+ lock->holder_line = line;
+
+ /* delete from thread holder list */
+ /* no need to lock other lockstructs, because they are all on the
+ * held-locks list, and this threads holds their locks.
+ * we only touch the thr->num members, so it is safe. */
+ if(thr->holding_first == lock)
+ thr->holding_first = lock->next_held_lock[thr->num];
+ if(thr->holding_last == lock)
+ thr->holding_last = lock->prev_held_lock[thr->num];
+ if(lock->next_held_lock[thr->num])
+ lock->next_held_lock[thr->num]->prev_held_lock[thr->num] =
+ lock->prev_held_lock[thr->num];
+ if(lock->prev_held_lock[thr->num])
+ lock->prev_held_lock[thr->num]->next_held_lock[thr->num] =
+ lock->next_held_lock[thr->num];
+ lock->next_held_lock[thr->num] = NULL;
+ lock->prev_held_lock[thr->num] = NULL;
+
+ /* store memory areas that are protected, for later checks */
+ prot_store(lock);
+ LOCKRET(pthread_mutex_unlock(&lock->lock));
+
+ /* unlock it */
+ switch(type) {
+ case check_lock_mutex:
+ LOCKRET(pthread_mutex_unlock(&lock->mutex));
+ break;
+ case check_lock_spinlock:
+ LOCKRET(pthread_spin_unlock(&lock->spinlock));
+ break;
+ case check_lock_rwlock:
+ LOCKRET(pthread_rwlock_unlock(&lock->rwlock));
+ break;
+ default:
+ log_assert(0);
+ }
+}
+
+/** checklock thread main, Inits thread structure. */
+static void* checklock_main(void* arg)
+{
+ struct thr_check* thr = (struct thr_check*)arg;
+ void* ret;
+ thr->id = pthread_self();
+ /* Hack to get same numbers as in log file */
+ thr->num = *(int*)(thr->arg);
+ log_assert(thread_infos[thr->num] == NULL);
+ thread_infos[thr->num] = thr;
+ LOCKRET(pthread_setspecific(thr_debug_key, thr));
+ ret = thr->func(thr->arg);
+ thread_infos[thr->num] = NULL;
+ free(thr);
+ return ret;
+}
+
+/** allocate debug info and create thread */
+void
+checklock_thrcreate(pthread_t* id, void* (*func)(void*), void* arg)
+{
+ struct thr_check* thr = (struct thr_check*)calloc(1,
+ sizeof(struct thr_check));
+ if(!thr)
+ fatal_exit("thrcreate: out of memory");
+ if(!key_created) {
+ struct thr_check* thisthr = (struct thr_check*)calloc(1,
+ sizeof(struct thr_check));
+ if(!thisthr)
+ fatal_exit("thrcreate: out of memory");
+ key_created = 1;
+ LOCKRET(pthread_key_create(&thr_debug_key, NULL));
+ LOCKRET(pthread_setspecific(thr_debug_key, thisthr));
+ thread_infos[0] = thisthr;
+ }
+ thr->func = func;
+ thr->arg = arg;
+ LOCKRET(pthread_create(id, NULL, checklock_main, thr));
+}
+
+/** signal handler for join timeout, Exits. */
+static RETSIGTYPE joinalarm(int ATTR_UNUSED(sig))
+{
+ fatal_exit("join thread timeout. hangup or deadlock.");
+}
+
+/** wait for thread with a timeout. */
+void
+checklock_thrjoin(pthread_t thread)
+{
+ /* wait with a timeout */
+ if(signal(SIGALRM, joinalarm) == SIG_ERR)
+ fatal_exit("signal(): %s", strerror(errno));
+ (void)alarm(CHECK_JOIN_TIMEOUT);
+ LOCKRET(pthread_join(thread, NULL));
+ (void)alarm(0);
+}
+
+#endif /* USE_THREAD_DEBUG */
--- /dev/null
+/**
+ * testcode/checklocks.h - wrapper on locks that checks access.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TESTCODE_CHECK_LOCKS_H
+#define TESTCODE_CHECK_LOCKS_H
+
+/**
+ * \file
+ * Locks that are checked.
+ *
+ * Holds information per lock and per thread.
+ * That information is protected by a mutex (unchecked).
+ *
+ * Checks:
+ * o which func, file, line created the lock.
+ * o contention count, measures amount of contention on the lock.
+ * o the memory region(s) that the lock protects are
+ * memcmp'ed to ascertain no race conditions.
+ * o checks that locks are unlocked properly (before deletion).
+ * keeps which func, file, line that locked it.
+ *
+ * Limitations:
+ * o Detects unprotected memory access when the lock is locked or freed,
+ * which detects races only if they happen, and only if in protected
+ * memory areas.
+ * o Detects deadlocks by timeout, so approximately, as they happen.
+ * o Does not check order of locking.
+ * o Uses a lot of memory.
+ * o The checks use locks themselves, changing scheduling,
+ * thus affecting the races that you see.
+ * o for rwlocks does not detect exclusive writelock, or double locking.
+ */
+
+#ifdef USE_THREAD_DEBUG
+#ifndef HAVE_PTHREAD
+/* really pretty arbitrary, since it will work with solaris threads too */
+#error "Need pthreads for checked locks"
+#endif
+/******************* THREAD DEBUG ************************/
+#include <pthread.h>
+
+/** How long to wait before lock attempt is a failure. */
+#define CHECK_LOCK_TIMEOUT 5 /* seconds */
+/** How long to wait before join attempt is a failure. */
+#define CHECK_JOIN_TIMEOUT 120 /* seconds */
+/** How many trheads to allocate for */
+#define THRDEBUG_MAX_THREADS 32 /* threads */
+
+/**
+ * Protection memory area.
+ * It is copied to a holding buffer to compare against later.
+ * Note that it may encompass the lock structure.
+ */
+struct protected_area {
+ /** where the memory region starts */
+ void* region;
+ /** size of the region */
+ size_t size;
+ /** backbuffer that holds a copy, of same size. */
+ void* hold;
+ /** next protected area in list */
+ struct protected_area* next;
+};
+
+/**
+ * per thread information for locking debug wrappers.
+ */
+struct thr_check {
+ /** thread id */
+ pthread_t id;
+ /** real thread func */
+ void* (*func)(void*);
+ /** func user arg */
+ void* arg;
+ /** number of thread in list structure */
+ int num;
+ /**
+ * list of locks that this thread is holding, double
+ * linked list, which first element the most recent lock acquired.
+ * So a represents the stack of locks acquired. (of all types).
+ */
+ struct checked_lock *holding_first, *holding_last;
+ /** if the thread is currently waiting for a lock, which one */
+ struct checked_lock* waiting;
+};
+
+/**
+ * One structure for all types of locks.
+ */
+struct checked_lock {
+ /** mutex for exclusive access to this structure */
+ pthread_mutex_t lock;
+ /** list of memory regions protected by this checked lock */
+ struct protected_area* prot;
+ /** where was this lock created */
+ const char* create_func, *create_file;
+ /** where was this lock created */
+ int create_line;
+ /** contention count */
+ size_t contention_count;
+ /** hold count (how many threads are holding this lock) */
+ int hold_count;
+ /** how many threads are waiting for this lock */
+ int wait_count;
+ /** who touched it last */
+ const char* holder_func, *holder_file;
+ /** who touched it last */
+ int holder_line;
+ /** who owns the lock now */
+ struct thr_check* holder;
+
+ /** next lock a thread is holding (less recent) */
+ struct checked_lock* next_held_lock[THRDEBUG_MAX_THREADS];
+ /** prev lock a thread is holding (more recent) */
+ struct checked_lock* prev_held_lock[THRDEBUG_MAX_THREADS];
+
+ /** type of lock */
+ enum check_lock_type {
+ /** basic mutex */
+ check_lock_mutex,
+ /** fast spinlock */
+ check_lock_spinlock,
+ /** rwlock */
+ check_lock_rwlock
+ } type;
+ /** the lock itself, see type to disambiguate the union */
+ union {
+ /** mutex */
+ pthread_mutex_t mutex;
+ /** spinlock */
+ pthread_spinlock_t spinlock;
+ /** rwlock */
+ pthread_rwlock_t rwlock;
+ };
+};
+
+/**
+ * Additional call for the user to specify what areas are protected
+ * @param lock: the lock that protects the area. It can be inside the area.
+ * @param area: ptr to mem.
+ * @param size: length of area.
+ * You can call it multiple times with the same lock to give several areas.
+ */
+void lock_protect(struct checked_lock* lock, void* area, size_t size);
+
+/**
+ * Init locks.
+ * @param type: what type of lock this is.
+ * @param lock: ptr to user alloced ptr structure. This is inited.
+ * So an alloc is done and the ptr is stored as result.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_init(enum check_lock_type type, struct checked_lock** lock,
+ const char* func, const char* file, int line);
+
+/**
+ * Destroy locks. Free the structure.
+ * @param type: what type of lock this is.
+ * @param lock: ptr to user alloced structure. This is destroyed.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
+ const char* func, const char* file, int line);
+
+/**
+ * Acquire readlock.
+ * @param type: what type of lock this is. Had better be a rwlock.
+ * @param lock: ptr to lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_rdlock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line);
+
+/**
+ * Acquire writelock.
+ * @param type: what type of lock this is. Had better be a rwlock.
+ * @param lock: ptr to lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_wrlock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line);
+
+/**
+ * Locks.
+ * @param type: what type of lock this is. Had better be mutex or spinlock.
+ * @param lock: the lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_lock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line);
+
+/**
+ * Unlocks.
+ * @param type: what type of lock this is.
+ * @param lock: the lock.
+ * @param func: caller function name.
+ * @param file: caller file name.
+ * @param line: caller line number.
+ */
+void checklock_unlock(enum check_lock_type type, struct checked_lock* lock,
+ const char* func, const char* file, int line);
+
+/**
+ * Create thread.
+ * @param thr: Thread id, where to store result.
+ * @param func: thread start function.
+ * @param arg: user argument.
+ */
+void checklock_thrcreate(pthread_t* thr, void* (*func)(void*), void* arg);
+
+/**
+ * Wait for thread to exit. Returns thread return value.
+ * @param thread: thread to wait for.
+ */
+void checklock_thrjoin(pthread_t thread);
+
+/** structures to enable compiler type checking on the locks.
+ * Also the pointer makes it so that the lock can be part of the protected
+ * region without any possible problem (since the ptr will stay the same.)
+ */
+struct checked_lock_rw { struct checked_lock* c_rw; };
+/** structures to enable compiler type checking on the locks. */
+struct checked_lock_mutex { struct checked_lock* c_m; };
+/** structures to enable compiler type checking on the locks. */
+struct checked_lock_spl { struct checked_lock* c_spl; };
+
+/** debugging rwlock */
+typedef struct checked_lock_rw lock_rw_t;
+#define lock_rw_init(lock) checklock_init(check_lock_rwlock, &((lock)->c_rw), __func__, __FILE__, __LINE__)
+#define lock_rw_destroy(lock) checklock_destroy(check_lock_rwlock, &((lock)->c_rw), __func__, __FILE__, __LINE__)
+#define lock_rw_rdlock(lock) checklock_rdlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__)
+#define lock_rw_wrlock(lock) checklock_wrlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__)
+#define lock_rw_unlock(lock) checklock_unlock(check_lock_rwlock, (lock)->c_rw, __func__, __FILE__, __LINE__)
+
+/** debugging mutex */
+typedef struct checked_lock_mutex lock_basic_t;
+#define lock_basic_init(lock) checklock_init(check_lock_mutex, &((lock)->c_m), __func__, __FILE__, __LINE__)
+#define lock_basic_destroy(lock) checklock_destroy(check_lock_mutex, &((lock)->c_m), __func__, __FILE__, __LINE__)
+#define lock_basic_lock(lock) checklock_lock(check_lock_mutex, (lock)->c_m, __func__, __FILE__, __LINE__)
+#define lock_basic_unlock(lock) checklock_unlock(check_lock_mutex, (lock)->c_m, __func__, __FILE__, __LINE__)
+
+/** debugging spinlock */
+typedef struct checked_lock_spl lock_quick_t;
+#define lock_quick_init(lock) checklock_init(check_lock_spinlock, &((lock)->c_spl), __func__, __FILE__, __LINE__)
+#define lock_quick_destroy(lock) checklock_destroy(check_lock_spinlock, &((lock)->c_spl), __func__, __FILE__, __LINE__)
+#define lock_quick_lock(lock) checklock_lock(check_lock_spinlock, (lock)->c_spl, __func__, __FILE__, __LINE__)
+#define lock_quick_unlock(lock) checklock_unlock(check_lock_spinlock, (lock)->c_spl, __func__, __FILE__, __LINE__)
+
+/** we use the pthread id, our thr_check structure is kept behind the scenes */
+typedef pthread_t ub_thread_t;
+#define ub_thread_create(thr, func, arg) checklock_thrcreate(thr, func, arg)
+#define ub_thread_self() pthread_self()
+#define ub_thread_join(thread) checklock_thrjoin(thread)
+
+typedef pthread_key_t ub_thread_key_t;
+#define ub_thread_key_create(key, f) LOCKRET(pthread_key_create(key, f))
+#define ub_thread_key_set(key, v) LOCKRET(pthread_setspecific(key, v))
+#define ub_thread_key_get(key) pthread_getspecific(key)
+
+#endif /* USE_THREAD_DEBUG */
+
+#endif /* TESTCODE_CHECK_LOCKS_H */