122 lines
3.9 KiB
C
122 lines
3.9 KiB
C
|
|
#ifndef _AKERR_LOCK_H_
|
||
|
|
#define _AKERR_LOCK_H_
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Serialization for the library's process-global state: the error pool
|
||
|
|
* (AKERR_ARRAY_ERROR) and the status registry. Private to the library -- none
|
||
|
|
* of this appears in the installed header, so the backend is not part of the
|
||
|
|
* ABI and can be changed without touching a consumer.
|
||
|
|
*
|
||
|
|
* The backend is chosen at configure time by the AKERR_THREADS build option and
|
||
|
|
* never by autodetection here. A build that quietly decided it did not need
|
||
|
|
* locking is exactly the failure this has to prevent: it would produce a
|
||
|
|
* library that reports itself thread safe and is not.
|
||
|
|
*
|
||
|
|
* AKERR_THREADS_PTHREAD POSIX threads.
|
||
|
|
* AKERR_THREADS_NONE No locking at all, for a build that has declared
|
||
|
|
* itself single threaded (-DAKERR_THREADS=none).
|
||
|
|
*
|
||
|
|
* One lock covers both tables, and it is recursive. Both are deliberate:
|
||
|
|
*
|
||
|
|
* - Raising an error re-enters the library. FAIL() calls
|
||
|
|
* akerr_name_for_status() to render the status into the stack trace and
|
||
|
|
* ENSURE_ERROR_READY() to check a context out of the pool, so a refusal
|
||
|
|
* raised from inside a locked registry operation takes the lock again on
|
||
|
|
* the same thread. A non-recursive mutex deadlocks there.
|
||
|
|
* - With a single lock there is no lock ordering to get wrong, and no way for
|
||
|
|
* a future caller to acquire the pool and the registry in the opposite
|
||
|
|
* order from this file.
|
||
|
|
*
|
||
|
|
* The cost is that error *construction* is serialized across threads. Errors
|
||
|
|
* are the exceptional path; correctness is worth more there than throughput.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
* PTHREAD_MUTEX_RECURSIVE is XSI, so glibc hides it under a strict -std=c99
|
||
|
|
* without _XOPEN_SOURCE. No feature-test macro is defined here, because the
|
||
|
|
* public header already needs the same one for PATH_MAX: a build strict enough
|
||
|
|
* to lose one has already lost the other. Build with -D_XOPEN_SOURCE=700 if you
|
||
|
|
* need strict C99.
|
||
|
|
*/
|
||
|
|
#if defined(AKERR_THREADS_PTHREAD) && AKERR_THREADS_PTHREAD == 1
|
||
|
|
|
||
|
|
#include <pthread.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
|
||
|
|
typedef pthread_mutex_t akerr_Mutex;
|
||
|
|
typedef pthread_once_t akerr_Once;
|
||
|
|
#define AKERR_ONCE_INIT PTHREAD_ONCE_INIT
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Terminal on failure. There is no error context to raise into: the pool one
|
||
|
|
* would come from is the thing this lock protects, and every path that could
|
||
|
|
* report the failure needs the lock to do it. A process whose error library
|
||
|
|
* silently stopped locking is worse than one that stops here.
|
||
|
|
*/
|
||
|
|
static void akerr_mutex_init(akerr_Mutex *mutex)
|
||
|
|
{
|
||
|
|
pthread_mutexattr_t attr;
|
||
|
|
|
||
|
|
if ( pthread_mutexattr_init(&attr) != 0 ||
|
||
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0 ||
|
||
|
|
pthread_mutex_init(mutex, &attr) != 0 ) {
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
pthread_mutexattr_destroy(&attr);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void akerr_mutex_lock(akerr_Mutex *mutex)
|
||
|
|
{
|
||
|
|
pthread_mutex_lock(mutex);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void akerr_mutex_unlock(akerr_Mutex *mutex)
|
||
|
|
{
|
||
|
|
pthread_mutex_unlock(mutex);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void akerr_once(akerr_Once *once, void (*routine)(void))
|
||
|
|
{
|
||
|
|
pthread_once(once, routine);
|
||
|
|
}
|
||
|
|
|
||
|
|
#elif defined(AKERR_THREADS_NONE) && AKERR_THREADS_NONE == 1
|
||
|
|
|
||
|
|
typedef char akerr_Mutex;
|
||
|
|
typedef int akerr_Once;
|
||
|
|
#define AKERR_ONCE_INIT 0
|
||
|
|
|
||
|
|
static void akerr_mutex_init(akerr_Mutex *mutex)
|
||
|
|
{
|
||
|
|
(void)mutex;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void akerr_mutex_lock(akerr_Mutex *mutex)
|
||
|
|
{
|
||
|
|
(void)mutex;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void akerr_mutex_unlock(akerr_Mutex *mutex)
|
||
|
|
{
|
||
|
|
(void)mutex;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The flag is raised before the routine runs, so a routine that calls back into
|
||
|
|
* akerr_init() sees initialization already in progress and does not recurse --
|
||
|
|
* the same short-circuit the pthread backend gets from akerr_initializing.
|
||
|
|
*/
|
||
|
|
static void akerr_once(akerr_Once *once, void (*routine)(void))
|
||
|
|
{
|
||
|
|
if ( *once == 0 ) {
|
||
|
|
*once = 1;
|
||
|
|
routine();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#else
|
||
|
|
#error "No threading backend selected. Build libakerror through its CMake, which defines AKERR_THREADS_PTHREAD or AKERR_THREADS_NONE from the AKERR_THREADS option."
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif // _AKERR_LOCK_H_
|