#include <freeradius-devel/util/dlist.h>
#include <freeradius-devel/util/atexit.h>
-
+#ifdef HAVE_PTHREADS
#include <pthread.h>
+#endif
#if defined(DEBUG_ATEXIT) && !defined(NDEBUG)
# define ATEXIT_DEBUG FR_FAULT_LOG
///< to ensure this memory is cleaned up.
};
+#ifdef HAVE_PTHREADS
static _Thread_local fr_atexit_list_t *fr_atexit_thread_local = NULL;
static fr_atexit_list_t *fr_atexit_threads = NULL;
-static fr_atexit_list_t *fr_atexit_global = NULL;
static pthread_mutex_t fr_atexit_global_mutex = PTHREAD_MUTEX_INITIALIZER;
-static bool is_exiting;
+#endif
+static fr_atexit_list_t *fr_atexit_global = NULL;
+static bool is_exiting;
/** Call the exit handler
*
return e;
}
-/** Talloc destructor for freeing list elements in order
- *
- */
-static int _thread_local_list_free(fr_atexit_list_t *list)
-{
- ATEXIT_DEBUG("%s - Freeing _Thread_local destructor list %p", __FUNCTION__, list);
-
- fr_dlist_talloc_free(&list->head); /* Free in order */
- list->e->func = NULL; /* Disarm the global entry that'd free the thread-specific list */
- return 0;
-}
-
-/** Run all the thread local destructors
- *
- * @param[in] list The thread-specific exit handler list.
- */
-static void _thread_local_pthread_free(void *list)
-{
- talloc_free(list);
-}
-
-/** Run all the thread local destructors
- *
- * @param[in] list The thread-specific exit handler list.
- */
-static int _thread_local_free(void *list)
-{
- return talloc_free(list);
-}
-
/** Talloc destructor for freeing list elements in order
*
*/
*/
static void _global_free(void)
{
+#ifdef HAVE_PTHREADS
pthread_mutex_lock(&fr_atexit_global_mutex);
+#endif
+
fr_cond_assert_msg(!is_exiting, "Global free function called multiple times");
is_exiting = true;
- pthread_mutex_unlock(&fr_atexit_global_mutex);
+#ifdef HAVE_PTHREADS
+ pthread_mutex_unlock(&fr_atexit_global_mutex);
TALLOC_FREE(fr_atexit_threads); /* Forcefully cleanup any thread-specific memory */
+#endif
TALLOC_FREE(fr_atexit_global);
}
fr_dlist_talloc_init(&fr_atexit_global->head, fr_atexit_entry_t, entry);
talloc_set_destructor(fr_atexit_global, _destructor_list_free);
+#ifdef HAVE_PTHREADS
fr_atexit_threads = talloc_zero(NULL, fr_atexit_list_t);
if (unlikely(!fr_atexit_threads)) return -1;
fr_dlist_talloc_init(&fr_atexit_threads->head, fr_atexit_entry_t, entry);
talloc_set_destructor(fr_atexit_threads, _destructor_list_free);
+#endif
atexit(_global_free); /* Call all remaining destructors at process exit */
return 0;
}
+#ifdef HAVE_PTHREADS
#define CHECK_GLOBAL_SETUP() \
do { \
int _ret = 0; \
pthread_mutex_unlock(&fr_atexit_global_mutex); \
if (_ret < 0) return _ret; \
} while(0)
+#else
+#define CHECK_GLOBAL_SETUP() \
+do { \
+ int _ret = 0; \
+ fr_cond_assert_msg(!is_exiting, "New atexit handlers should not be allocated whilst exiting"); \
+ if (!fr_atexit_global) _ret = fr_atexit_global_setup(); \
+ if (_ret < 0) return _ret; \
+} while(0)
+#endif
/** Add a free function to be called when the process exits
*
return 0;
}
-/** Add a new destructor
- *
- * @return
- * - 0 on success.
- * - -1 on memory allocation failure;
- */
-int _fr_atexit_thread_local(NDEBUG_LOCATION_ARGS
- fr_atexit_t func, void const *uctx)
-{
- CHECK_GLOBAL_SETUP();
-
- /*
- * Initialise the thread local list, just for pthread_exit().
- */
- if (!fr_atexit_thread_local) {
- fr_atexit_list_t *list;
-
- /*
- * Must be heap allocated, because thread local
- * structures can be freed before the key
- * destructor is run (depending on platform).
- */
- list = talloc_zero(NULL, fr_atexit_list_t);
- if (unlikely(!list)) return -1;
-
- ATEXIT_DEBUG("%s - Thread %u alloced _Thread_local destructor list %p",
- __FUNCTION__,
- (unsigned int)pthread_self(), list);
-
- fr_dlist_talloc_init(&list->head, fr_atexit_entry_t, entry);
- (void) pthread_key_create(&list->key, _thread_local_pthread_free);
-
- /*
- * We need to pass in a pointer to the heap
- * memory because, again, the thread local
- * indirection table may have disappeared
- * by the time the thread destructor is
- * called.
- */
- (void) pthread_setspecific(list->key, list);
- talloc_set_destructor(list, _thread_local_list_free);
-
- /*
- * Add a destructor for the thread-local list
- * The pthread based destructor will disarm
- * this if it fires, but leave it enabled if
- * it doesn't, thus ensuring the memory is
- * *always* freed one way or another.
- */
- pthread_mutex_lock(&fr_atexit_global_mutex);
- list->e = atexit_entry_alloc(NDEBUG_LOCATION_VALS
- fr_atexit_threads,
- _thread_local_free,
- list);
-
- pthread_mutex_unlock(&fr_atexit_global_mutex);
-
- fr_atexit_thread_local = list;
- }
-
- /*
- * Now allocate the actual atexit handler entry
- */
- if (atexit_entry_alloc(NDEBUG_LOCATION_VALS fr_atexit_thread_local, func, uctx) == NULL) return -1;
-
- return 0;
-}
-
-/** Remove a specific destructor for this thread (without executing them)
- *
- * @note This function's primary purpose is to help diagnose issues with destructors
- * from within a debugger.
- *
- * @param[in] uctx_scope Only process entries where the func and scope both match.
- * @param[in] func Entries matching this function will be disarmed.
- * @param[in] uctx associated with the entry.
- * @return How many destructors were disarmed.
- */
-unsigned int fr_atexit_thread_local_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx)
-{
- fr_atexit_entry_t *e = NULL;
- unsigned int count = 0;
-
- if (!fr_atexit_thread_local) return -1;
-
- while ((e = fr_dlist_next(&fr_atexit_thread_local->head, e))) {
- fr_atexit_entry_t *disarm;
-
- if ((e->func != func) || ((e->uctx != uctx) && uctx_scope)) continue;
-
- ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
- __FUNCTION__,
- (unsigned int)pthread_self(),
- fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
- disarm = e;
- e = fr_dlist_remove(&fr_atexit_thread_local->head, e);
- talloc_set_destructor(disarm, NULL);
- talloc_free(disarm);
-
- count++;
- }
-
- return count;
-}
-
-/** Remove all destructors for this thread (without executing them)
- *
- * @note This function's primary purpose is to help diagnose issues with destructors
- * from within a debugger.
- */
-void fr_atexit_thread_local_disarm_all(void)
-{
- fr_atexit_entry_t *e = NULL;
-
- if (!fr_atexit_thread_local) return;
-
- while ((e = fr_dlist_pop_head(&fr_atexit_thread_local->head))) {
- ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
- __FUNCTION__,
- (unsigned int)pthread_self(),
- fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
- talloc_set_destructor(e, NULL);
- talloc_free(e);
- }
-}
-
-/** Cause all thread local free triggers to fire
- *
- * This is necessary when we're running in single threaded mode
- * to ensure all "thread-local" memory (which isn't actually thread local)
- * is cleaned up.
- *
- * One example is the OpenSSL log BIOs which must be cleaned up
- * before fr_openssl_free is called.
- *
- * @return
- * - >= 0 The number of atexit handlers triggered on success.
- * - <0 the return code from any atexit handlers that returned an error.
- */
-int fr_atexit_thread_trigger_all(void)
-{
- fr_atexit_entry_t *e = NULL, *ee, *to_free;
- fr_atexit_list_t *list;
- unsigned int count = 0;
-
- /*
- * Iterate over the list of thread local
- * destructor lists running the
- * destructors.
- */
- while ((e = fr_dlist_next(&fr_atexit_threads->head, e))) {
- if (!e->func) continue; /* thread already joined */
-
- list = talloc_get_type_abort(e->uctx, fr_atexit_list_t);
- ee = NULL;
- while ((ee = fr_dlist_next(&list->head, ee))) {
- ATEXIT_DEBUG("%s - Thread %u triggering %p/%p func=%p, uctx=%p (alloced %s:%u)",
- __FUNCTION__,
- (unsigned int)pthread_self(),
- list, ee, ee->func, ee->uctx, ee->file, ee->line);
-
- count++;
- to_free = ee;
- ee = fr_dlist_remove(&list->head, ee);
- if (talloc_free(to_free) < 0) {
- fr_strerror_printf_push("atexit handler failed %p/%p func=%p, uctx=%p"
-#ifndef NDEBUG
- " (alloced %s:%u)"
-#endif
- ,
- list, to_free,
- to_free->func, to_free->uctx
-#ifndef NDEBUG
- , to_free->file, to_free->line
-#endif
- );
- return -1;
- }
- }
- }
-
- return count;
-}
-
/** Remove a specific global destructor (without executing it)
*
* @note This function's primary purpose is to help diagnose issues with destructors
fr_atexit_entry_t *e = NULL;
unsigned int count = 0;
- if (!fr_atexit_thread_local) return -1;
-
while ((e = fr_dlist_next(&fr_atexit_global->head, e))) {
fr_atexit_entry_t *disarm;
*/
int fr_atexit_trigger(bool uctx_scope, fr_atexit_t func, void const *uctx)
{
- fr_atexit_entry_t *e = NULL, *ee, *to_free;
+ fr_atexit_entry_t *e = NULL, *to_free;
+#ifdef HAVE_PTHREADS
+ fr_atexit_entry_t *ee;
fr_atexit_list_t *list;
+#endif
unsigned int count = 0;
if (!fr_atexit_global) goto do_threads;
e = NULL;
do_threads:
+#ifdef HAVE_PTHREADS
if (!fr_atexit_threads) return 0;
/*
}
}
}
+#endif
return count;
}
{
return is_exiting;
}
+
+#ifdef HAVE_PTHREADS
+/** Talloc destructor for freeing list elements in order
+ *
+ */
+static int _thread_local_list_free(fr_atexit_list_t *list)
+{
+ ATEXIT_DEBUG("%s - Freeing _Thread_local destructor list %p", __FUNCTION__, list);
+
+ fr_dlist_talloc_free(&list->head); /* Free in order */
+ list->e->func = NULL; /* Disarm the global entry that'd free the thread-specific list */
+ return 0;
+}
+
+/** Run all the thread local destructors
+ *
+ * @param[in] list The thread-specific exit handler list.
+ */
+static void _thread_local_pthread_free(void *list)
+{
+ talloc_free(list);
+}
+
+/** Run all the thread local destructors
+ *
+ * @param[in] list The thread-specific exit handler list.
+ */
+static int _thread_local_free(void *list)
+{
+ return talloc_free(list);
+}
+
+/** Add a new destructor
+ *
+ * @return
+ * - 0 on success.
+ * - -1 on memory allocation failure;
+ */
+int _fr_atexit_thread_local(NDEBUG_LOCATION_ARGS
+ fr_atexit_t func, void const *uctx)
+{
+ CHECK_GLOBAL_SETUP();
+
+ /*
+ * Initialise the thread local list, just for pthread_exit().
+ */
+ if (!fr_atexit_thread_local) {
+ fr_atexit_list_t *list;
+
+ /*
+ * Must be heap allocated, because thread local
+ * structures can be freed before the key
+ * destructor is run (depending on platform).
+ */
+ list = talloc_zero(NULL, fr_atexit_list_t);
+ if (unlikely(!list)) return -1;
+
+ ATEXIT_DEBUG("%s - Thread %u alloced _Thread_local destructor list %p",
+ __FUNCTION__,
+ (unsigned int)pthread_self(), list);
+
+ fr_dlist_talloc_init(&list->head, fr_atexit_entry_t, entry);
+ (void) pthread_key_create(&list->key, _thread_local_pthread_free);
+
+ /*
+ * We need to pass in a pointer to the heap
+ * memory because, again, the thread local
+ * indirection table may have disappeared
+ * by the time the thread destructor is
+ * called.
+ */
+ (void) pthread_setspecific(list->key, list);
+ talloc_set_destructor(list, _thread_local_list_free);
+
+ /*
+ * Add a destructor for the thread-local list
+ * The pthread based destructor will disarm
+ * this if it fires, but leave it enabled if
+ * it doesn't, thus ensuring the memory is
+ * *always* freed one way or another.
+ */
+ pthread_mutex_lock(&fr_atexit_global_mutex);
+ list->e = atexit_entry_alloc(NDEBUG_LOCATION_VALS
+ fr_atexit_threads,
+ _thread_local_free,
+ list);
+
+ pthread_mutex_unlock(&fr_atexit_global_mutex);
+
+ fr_atexit_thread_local = list;
+ }
+
+ /*
+ * Now allocate the actual atexit handler entry
+ */
+ if (atexit_entry_alloc(NDEBUG_LOCATION_VALS fr_atexit_thread_local, func, uctx) == NULL) return -1;
+
+ return 0;
+}
+
+/** Remove a specific destructor for this thread (without executing them)
+ *
+ * @note This function's primary purpose is to help diagnose issues with destructors
+ * from within a debugger.
+ *
+ * @param[in] uctx_scope Only process entries where the func and scope both match.
+ * @param[in] func Entries matching this function will be disarmed.
+ * @param[in] uctx associated with the entry.
+ * @return How many destructors were disarmed.
+ */
+unsigned int fr_atexit_thread_local_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx)
+{
+ fr_atexit_entry_t *e = NULL;
+ unsigned int count = 0;
+
+ if (!fr_atexit_thread_local) return -1;
+
+ while ((e = fr_dlist_next(&fr_atexit_thread_local->head, e))) {
+ fr_atexit_entry_t *disarm;
+
+ if ((e->func != func) || ((e->uctx != uctx) && uctx_scope)) continue;
+
+ ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ (unsigned int)pthread_self(),
+ fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
+ disarm = e;
+ e = fr_dlist_remove(&fr_atexit_thread_local->head, e);
+ talloc_set_destructor(disarm, NULL);
+ talloc_free(disarm);
+
+ count++;
+ }
+
+ return count;
+}
+
+/** Remove all destructors for this thread (without executing them)
+ *
+ * @note This function's primary purpose is to help diagnose issues with destructors
+ * from within a debugger.
+ */
+void fr_atexit_thread_local_disarm_all(void)
+{
+ fr_atexit_entry_t *e = NULL;
+
+ if (!fr_atexit_thread_local) return;
+
+ while ((e = fr_dlist_pop_head(&fr_atexit_thread_local->head))) {
+ ATEXIT_DEBUG("%s - Thread %u disarming %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ (unsigned int)pthread_self(),
+ fr_atexit_thread_local, e, e->func, e->uctx, e->file, e->line);
+ talloc_set_destructor(e, NULL);
+ talloc_free(e);
+ }
+}
+
+/** Cause all thread local free triggers to fire
+ *
+ * This is necessary when we're running in single threaded mode
+ * to ensure all "thread-local" memory (which isn't actually thread local)
+ * is cleaned up.
+ *
+ * One example is the OpenSSL log BIOs which must be cleaned up
+ * before fr_openssl_free is called.
+ *
+ * @return
+ * - >= 0 The number of atexit handlers triggered on success.
+ * - <0 the return code from any atexit handlers that returned an error.
+ */
+int fr_atexit_thread_trigger_all(void)
+{
+ fr_atexit_entry_t *e = NULL, *ee, *to_free;
+ fr_atexit_list_t *list;
+ unsigned int count = 0;
+
+ /*
+ * Iterate over the list of thread local
+ * destructor lists running the
+ * destructors.
+ */
+ while ((e = fr_dlist_next(&fr_atexit_threads->head, e))) {
+ if (!e->func) continue; /* thread already joined */
+
+ list = talloc_get_type_abort(e->uctx, fr_atexit_list_t);
+ ee = NULL;
+ while ((ee = fr_dlist_next(&list->head, ee))) {
+ ATEXIT_DEBUG("%s - Thread %u triggering %p/%p func=%p, uctx=%p (alloced %s:%u)",
+ __FUNCTION__,
+ (unsigned int)pthread_self(),
+ list, ee, ee->func, ee->uctx, ee->file, ee->line);
+
+ count++;
+ to_free = ee;
+ ee = fr_dlist_remove(&list->head, ee);
+ if (talloc_free(to_free) < 0) {
+ fr_strerror_printf_push("atexit handler failed %p/%p func=%p, uctx=%p"
+#ifndef NDEBUG
+ " (alloced %s:%u)"
+#endif
+ ,
+ list, to_free,
+ to_free->func, to_free->uctx
+#ifndef NDEBUG
+ , to_free->file, to_free->line
+#endif
+ );
+ return -1;
+ }
+ }
+ }
+
+ return count;
+}
+#endif
extern "C" {
#endif
-/*
- * Because GCC only added support in 2013 *sigh*
- */
-#ifdef TLS_STORAGE_CLASS
-# define _Thread_local TLS_STORAGE_CLASS
-#endif
-
/** Destructor callback
*
* @param[in] uctx to free.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
*/
typedef int(*fr_atexit_t)(void *uctx);
* - 0 on success.
* - -1 on failure.
*/
-#define fr_atexit_global(_func, _uctx) \
- _atexit_global(NDEBUG_LOCATION_EXP _func, _uctx)
+#define fr_atexit_global(_func, _uctx) _atexit_global(NDEBUG_LOCATION_EXP _func, _uctx)
+
+unsigned int fr_atexit_global_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx);
+
+void fr_atexit_global_disarm_all(void);
+
+int fr_atexit_global_trigger_all(void);
+
+int fr_atexit_trigger(bool uctx_scope, fr_atexit_t func, void const *uctx);
+
+bool fr_atexit_is_exiting(void);
+
+#ifdef HAVE_PTHREADS
+/*
+ * Because GCC only added support in 2013 *sigh*
+ */
+#ifdef TLS_STORAGE_CLASS
+# define _Thread_local TLS_STORAGE_CLASS
+#endif
/** Setup pair of global init/free functions
*
{ \
static atomic_bool _init_done = false; \
static pthread_mutex_t _init_mutex = PTHREAD_MUTEX_INITIALIZER; \
+ void *_our_uctx = _uctx; /* stop _uctx being evaluated multiple times, it may be a call to malloc() */ \
if (unlikely(!atomic_load(&_init_done))) { \
pthread_mutex_lock(&_init_mutex); \
if (!atomic_load(&_init_done)) { \
- _init(_uctx); \
- fr_atexit_global(_free, _uctx); \
+ _init(_our_uctx); \
+ fr_atexit_global(_free, _our_uctx); \
atomic_store(&_init_done, true); \
} \
pthread_mutex_unlock(&_init_mutex); \
} \
}
-
/** Set a destructor for thread local storage to free the memory on thread exit
*
* @note Pointers to thread local storage seem to become unusable as threads are
int fr_atexit_thread_trigger_all(void);
-unsigned int fr_atexit_global_disarm(bool uctx_scope, fr_atexit_t func, void const *uctx);
-
-void fr_atexit_global_disarm_all(void);
-
-int fr_atexit_global_trigger_all(void);
-
-int fr_atexit_trigger(bool uctx_scope, fr_atexit_t func, void const *uctx);
-
-bool fr_atexit_is_exiting(void);
+/*
+ * If we're building without threading support,
+ * all this becomes much easier, and we just map
+ * all thread local cleanup entries to the global
+ * list.
+ */
+#else
+/*
+ * Don't emit a _Thread_local_storage qualifier
+ */
+# define __Thread_local
+# define fr_atexit_global_once(_init, _free, _uctx) \
+do { \
+ static bool _init_done = false; \
+ void * _our_uctx = _uctx; /* stop _uctx being evaluated multiple times, it may be a call to malloc() */ \
+ if (unlikely(!_init_done)) { \
+ _init(_our_uctx); \
+ fr_atexit_global(_free, _our_uctx); \
+ _init_done = true; \
+ } \
+} while(0);
+# define fr_atexit_thread_local(_name, _free, _uctx) \
+do { \
+ static bool _init_done = false; \
+ void * _our_uctx = _uctx; /* stop _uctx being evaluated multiple times, it may be a call to malloc() */ \
+ if (unlikely(!_init_done)) { \
+ fr_atexit_global(_free, _our_uctx); \
+ _init_done = true; \
+ } \
+ _name = _our_uctx; \
+} while(0);
+# define fr_atexit_thread_local_disarm(...) fr_atexit_global_disarm(__VA_ARGS__)
+# define fr_atexit_thread_local_disarm_all(...) fr_atexit_global_disarm_all(__VA_ARGS__)
+# define fr_atexit_thread_trigger_all(...) fr_atexit_global_trigger_all(__VA_ARGS__)
+#endif
#ifdef __cplusplus
}