#include <freeradius-devel/util/dlist.h>
#include <freeradius-devel/util/atexit.h>
+#include <stdatomic.h>
+
#ifdef HAVE_PTHREADS
#endif
static bool is_exiting;
static _Thread_local bool thread_is_exiting;
+/** Latched once main has decided no more thread-local pools should be handed out
+ *
+ * `fr_atexit_thread_trigger_all()` runs every registered thread destructor on
+ * the calling (main) thread, including ones that free `_Thread_local` pools
+ * owned by threads we don't manage (librdkafka's bg threads, perl, etc.). We
+ * can free the underlying chunks but can't reset another thread's TLS slot,
+ * so those threads keep a dangling pointer in their per-thread cache. Code
+ * that lazily allocates a TLS-cached pool (log.c, sbuff.c, strerror.c, ...)
+ * checks this flag *before* reading its TLS slot and falls back to
+ * `talloc_*(NULL, ...)` (or returns NULL) when set, side-stepping the
+ * dangling pointer entirely.
+ *
+ * Set once, never cleared. Relaxed atomic because this is a one-shot signal
+ * with no other state synchronised through it - readers tolerate observing
+ * the old value briefly.
+ */
+static atomic_bool thread_local_alloc_disabled;
+
/** Call the exit handler
*
*/
}
+/** Disable lazy allocation of thread-local caches for the rest of the process
+ *
+ * Call this from main *before* `fr_atexit_thread_trigger_all()`. After it
+ * returns, every TLS-pool initialiser that consults
+ * `fr_atexit_thread_local_alloc_disabled()` falls back to the un-cached path,
+ * so `_Thread_local` slots in other threads aren't read after the trigger
+ * frees their backing memory.
+ */
+void fr_atexit_thread_local_disable_alloc(void)
+{
+ atomic_store_explicit(&thread_local_alloc_disabled, true, memory_order_relaxed);
+}
+
+/** Has @ref fr_atexit_thread_local_disable_alloc been called yet
+ *
+ */
+bool fr_atexit_thread_local_alloc_disabled(void)
+{
+ return atomic_load_explicit(&thread_local_alloc_disabled, memory_order_relaxed);
+}
+
/** Return whether we're currently in the teardown phase
*
* When this function returns true no more thread local or global
fr_atexit_list_t *list;
unsigned int count = 0;
+ /*
+ * Disable TLS-cached pool handouts before we start freeing
+ * any of them. Threads we don't manage (librdkafka's bg
+ * threads, etc.) keep dangling pointers in their
+ * `_Thread_local` slots once the chunks here are reaped, so
+ * any TLS-pool initialiser that consults the flag falls back
+ * to `talloc_*(NULL, ...)` from now on.
+ */
+ fr_atexit_thread_local_disable_alloc();
+
/*
* Iterate over the list of thread local
* destructor lists running the
TALLOC_CTX *pool;
/*
- * Post-trigger of every thread atexit handler the TLS slot
- * may be a dangling pointer for any thread we don't own
- * (librdkafka's bg threads etc.) - skip the pool entirely.
+ * Once main has signalled shutdown the TLS slot may be a
+ * dangling pointer for any thread we don't own (librdkafka's
+ * bg threads etc.) - skip the pool entirely.
*/
- if (unlikely(atomic_load_explicit(&log_pools_disabled, memory_order_relaxed))) return NULL;
+ if (unlikely(fr_atexit_thread_local_alloc_disabled())) return NULL;
pool = fr_log_pool;
if (unlikely(!pool)) {