#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 #include 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_