]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Allow variants of module lists
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 10 May 2024 04:21:14 +0000 (22:21 -0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 10 May 2024 05:47:05 +0000 (23:47 -0600)
For now we support global module lists and thread-local lists

src/lib/server/module.c
src/lib/server/module.h
src/lib/server/module_rlm.c
src/lib/server/virtual_servers.c
src/listen/detail/proto_detail.c

index 2a9624a2a66283b649cdf9eefe45092db5ee434c..a82b02fabcf5d3a38a4e9b603c1157604134ad02 100644 (file)
  * $Id$
  *
  * @file src/lib/server/module.c
- * @brief Defines functions for module (re-)initialisation.
+ * @brief Defines functions for module initialisation
  *
- * @copyright 2003,2006,2016 The FreeRADIUS server project
  * @copyright 2016,2024 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @copyright 2003,2006,2016 The FreeRADIUS server project
  * @copyright 2000 Alan DeKok (aland@freeradius.org)
  * @copyright 2000 Alan Curry (pacman@world.std.com)
  */
@@ -36,20 +36,142 @@ RCSID("$Id$")
 #include <freeradius-devel/server/request_data.h>
 #include <freeradius-devel/server/module.h>
 #include <freeradius-devel/util/strerror.h>
+#include <freeradius-devel/util/talloc.h>
 #include <freeradius-devel/unlang/xlat_func.h>
 
-/** Heap of all lists/modules used to get a common index with module_thread_inst_list
+#include <talloc.h>
+
+/** dl module tracking
  *
+ * This is used by all module lists, irrespecitve of their type, and is thread safe.
  */
-static fr_heap_t *module_global_inst_list;
+static dl_module_loader_t      *dl_modules = NULL;
 
-/** An array of thread-local module lists
+/** Callback to initialise any global structures required for the module list
+ *
+ * @param[in] ml       to initialise global data for.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+typedef int (*module_list_init_t)(module_list_t *ml);
+
+/** Callback to free any global structures associated with the module list
  *
- * The indexes in this array are identical to module_list_global, allowing
- * O(1) lookups.  Arrays are used here as there's no performance penalty
- * once they're populated.
+ * @param[in] ml       to free.
  */
-static _Thread_local module_thread_instance_t **module_thread_inst_list;
+typedef void (*module_list_free_t)(module_list_t *ml);
+
+/** Callback to add data for a module
+ *
+ * @param[in] mi       to add data for.
+ *                     Use mi->ml for the module list.
+ *                     Use mi->data to access the data.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+typedef int (*module_list_data_add_t)(module_instance_t *mi);
+
+/** Callback to del data for a module
+ *
+ * @param[in] mi       to add data to (use mi->ml for the module list).
+ *
+ */
+typedef void (*module_list_data_del_t)(module_instance_t *mi);
+
+/** Callback to initialise a list for thread-local data, called once per thread
+ *
+ * @param[in] ctx      talloc context for thread-local data.
+ *                     May be modified by the init function if the
+ *                     module_thread_instance_t need to be parented
+ *                     by another ctx.
+ * @param[in] ml       to initialise thread-local data for.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+typedef int (*module_list_thread_init_t)(TALLOC_CTX **ctx, module_list_t const *ml);
+
+/** Callback to free thread-local structures, called once per thread as the thread is being destroyed
+ *
+ * @param[in] ml       to free thread-local data for.
+ */
+typedef void (*module_list_thread_free_t)(module_list_t *ml);
+
+/** Callback to add thread-local data for a module
+ *
+ * @param[in] ti       to add data for.
+ *                     Use `ti->mi->ml` for the module list.
+ *                     Use `ti->mi` for the module instance.
+ *                     Use `ti->data` for the thread specific data.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+typedef int (*module_list_thread_data_add_t)(module_thread_instance_t *ti);
+
+/** Callback to remove thread-local data for a module
+ *
+ * @param[in] ti       to del data for.
+ *                     Use `ti->mi->ml` for the module list.
+ *                     Use `ti->mi` for the module instance.
+ *                     Use `ti->data` for the thread specific data.
+ */
+typedef void (*module_list_thread_data_del_t)(module_thread_instance_t *ti);
+
+/** Structure to hold callbacks for a module list type
+ *
+ * We care about performance for module lists, as they're used heavily at runtime.
+ *
+ * As much as possible we try to avoid jumping through unecessary functions and
+ * unecessary switch statements.
+ *
+ * This structure contains callbacks which change how the module list operates,
+ * making it either a global module list, or a thread-local module list, i.e. one
+ * which only be used by a single thread.
+ *
+ * Instances of this structure are created in this compilation unit, and exported
+ * for the caller to pass into module_list_alloc().
+ */
+struct module_list_type_s {
+       size_t                                  list_size;              //!< Size of talloc_chunk to allocate for the module_list_t.
+
+       module_list_init_t                      init;                   //!< Initialise any global structures required for thread-local lookups.
+       module_list_free_t                      free;                   //!< Free any global structures required for thread-local lookups.
+
+       size_t                                  inst_size;              //!< Size of talloc chunk to allocate for the module_instance_t.
+                                                                       ///< allows over-allocation if list types want to append fields.
+       module_list_data_add_t                  data_add;               //!< Record that module data has been added.
+       module_list_data_del_t                  data_del;               //!< Record that module data has been removed.
+
+       /** Callbacks to manage thread-local data
+        */
+       struct {
+               module_list_thread_init_t               init;                   //!< Initialise any thread-local structures required for thread-local lookups.
+               module_list_thread_free_t               free;                   //!< Free any thread-local structures.
+
+               module_list_thread_data_add_t           data_add;               //!< Add thread-local data for a module.
+               module_list_thread_data_get_t           data_get;               //!< Retrieve thread local-data for a module.
+               module_list_thread_data_del_t           data_del;               //!< Remove (but not free) thread-local data for a module.
+
+               void                                    *data;                  //!< Pointer to hold any global resources for the thread-local implementation.
+       } thread;
+};
+
+static void module_thread_detach(module_thread_instance_t *ti);
+
+/** Heap of all lists/modules used to get a common index with mlg_thread->inst_list
+ */
+static fr_heap_t *mlg_index;
+
+/** An array of thread-local module lists
+*
+* The indexes in this array are identical to module_list_global, allowing
+* O(1) lookups.  Arrays are used here as there's no performance penalty
+* once they're populated.
+*/
+static _Thread_local module_thread_instance_t **mlg_thread_inst_list;
 
 /** Toggle used to determine if it's safe to use index based lookups
  *
@@ -62,12 +184,254 @@ static _Thread_local module_thread_instance_t **module_thread_inst_list;
  * list so we need to revert back to doing binary searches instead of using
  * common indexes.
  */
-static _Thread_local bool module_list_in_sync = true;
+static _Thread_local bool mlg_in_sync;
 
-/** dl module tracking
+typedef struct {
+       module_instance_t               mi;             //!< Common module instance fields.  Must come first.
+
+       fr_heap_index_t                 inst_idx;       //!< Entry in the bootstrap/instantiation heap.
+                                                       //!< should be an identical value to the thread-specific
+                                                       ///< data for this module.
+} mlg_module_instance_t;
+
+/** Sort module instance data first by list then by number
  *
+ * The module's position in the global instance heap informs of us
+ * of its position in the thread-specific heap, which allows for
+ * O(1) lookups.
  */
-static dl_module_loader_t      *dl_modules = NULL;
+static int8_t _mlg_module_instance_cmp(void const *one, void const *two)
+{
+       module_instance_t const *a = talloc_get_type_abort_const(one, module_instance_t);
+       module_instance_t const *b = talloc_get_type_abort_const(two, module_instance_t);
+       int8_t ret;
+
+       fr_assert(a->ml && b->ml);
+
+       ret = CMP(a->ml, b->ml);
+       if (ret != 0) return 0;
+
+       return CMP(a->number, b->number);
+}
+
+/** Free the global module index
+ *
+ */
+static int _mlg_global_free(UNUSED void *uctx)
+{
+       return talloc_free(mlg_index);
+}
+
+/** Initialise the global module index
+ *
+ */
+static int _mlg_global_init(UNUSED void *uctx)
+{
+       MEM(mlg_index = fr_heap_alloc(NULL, _mlg_module_instance_cmp, mlg_module_instance_t, inst_idx, 256));
+       return 0;
+}
+
+/** Global initialisation for index heap and module array
+ *
+ */
+static int mlg_init(UNUSED module_list_t *ml)
+{
+       /*
+       *       Create the global module heap we use for
+       *       common indexes in the thread-specific
+       *       heaps.
+       */
+       fr_atexit_global_once(_mlg_global_init, _mlg_global_free, NULL);
+
+       return 0;
+}
+
+/** Add the unique index value so we can do thread local lookups
+ *
+ */
+static int mlg_data_add(module_instance_t *mi)
+{
+       /*
+        *      Insert the module into the global heap so
+        *      we can get common thread-local indexes.
+        */
+       if (fr_heap_insert(&mlg_index, mi) < 0) {
+               ERROR("Failed inserting into global module index");
+               return -1;
+       }
+
+       return 0;
+}
+
+static void mlg_data_del(module_instance_t *mi)
+{
+       mlg_module_instance_t   *mlg_mi = (mlg_module_instance_t *)talloc_get_type_abort(mi, module_instance_t);
+
+       if (fr_heap_entry_inserted(mlg_mi->inst_idx) && !fr_cond_assert(fr_heap_extract(&mlg_index, mi) == 0)) return;
+       return;
+}
+
+/** Free the thread local heap on exit
+ *
+ * All thread local module lists should have been destroyed by this point
+ */
+static int _module_thread_inst_list_free(void *tilp)
+{
+       module_thread_instance_t **til = talloc_get_type_abort(tilp, module_thread_instance_t *);
+       size_t i, len = talloc_array_length(til);
+       unsigned int found = 0;
+
+       for (i = 0; i < len; i++) if (til[i]) found++;
+
+       if (!fr_cond_assert_msg(found == 0,
+                               "Thread local array has %u non-null elements remaining on exit.  This is a leak",
+                               found)) {
+               return -1;
+       }
+
+       return talloc_free(til);
+}
+
+/** Allocate a thread-local array to hold thread data for each module thats been instantiated
+ *
+ * @param[in] ctx      Talloc context for the thread-local data.
+ *                     Mutated by this function so that thread local data is allocated
+ *                     beneath the array.
+ * @param[in] ml       Module list to initialise the thread-local data for.
+ */
+static int mlg_thread_init(UNUSED TALLOC_CTX **ctx, UNUSED module_list_t const *ml)
+{
+       /*
+        *      Initialise the thread specific tree if this is the
+        *      first time through or if everything else was
+        *      de-initialised.
+        */
+       if (!mlg_thread_inst_list) {
+               module_thread_instance_t **arr;
+
+               MEM(arr = talloc_zero_array(NULL, module_thread_instance_t *, fr_heap_num_elements(mlg_index)));
+
+               fr_atexit_thread_local(mlg_thread_inst_list, _module_thread_inst_list_free, arr);
+
+               mlg_in_sync = true;
+       }
+
+       return 0;
+}
+
+/** Retrieve the thread-specific data for a module from the thread-local array of instance data
+ *
+ * This looks complex, but it's just asserts for sanity.  This is really only returning an array offset.
+ *
+ * @param[in] mi       Module instance to get the thread-specific data for.
+ */
+static module_thread_instance_t *mlg_thread_data_get(module_instance_t *mi)
+{
+       mlg_module_instance_t           *mlg_mi = (mlg_module_instance_t *)talloc_get_type_abort(mi, module_instance_t);
+       module_thread_instance_t        *ti;
+
+       fr_assert_msg(mlg_mi->inst_idx <= talloc_array_length(mlg_thread_inst_list),
+                     "module instance index %u must be <= thread local array %zu",
+                     mlg_mi->inst_idx, talloc_array_length(mlg_thread_inst_list));
+       fr_assert(mlg_in_sync);
+
+       fr_assert_msg(fr_heap_num_elements(mlg_index) == talloc_array_length(mlg_thread_inst_list),
+                     "mismatch between global module heap (%u entries) and thread local (%zu entries)",
+                     fr_heap_num_elements(mlg_index), talloc_array_length(mlg_thread_inst_list));
+
+       ti = talloc_get_type_abort(mlg_thread_inst_list[mlg_mi->inst_idx - 1], module_thread_instance_t);
+       fr_assert_msg(ti->mi == mi, "thread/module mismatch thread %s (%p), module %s (%p)",
+                     ti->mi->name, ti->mi, mi->name, mi);
+
+       return ti;
+}
+
+static int mlg_thread_data_add(module_thread_instance_t *ti)
+{
+       mlg_module_instance_t *mlg_mi = (mlg_module_instance_t *)talloc_get_type_abort(ti->mi, module_instance_t);
+       mlg_thread_inst_list[mlg_mi->inst_idx - 1] = ti;
+       return 0;
+}
+
+static void mlg_thread_data_del(module_thread_instance_t *ti)
+{
+       mlg_module_instance_t *mlg_mi = (mlg_module_instance_t *)talloc_get_type_abort(ti->mi, module_instance_t);
+       mlg_thread_inst_list[mlg_mi->inst_idx - 1] = NULL;
+       mlg_in_sync = false;
+}
+
+/** Callbacks for a global module list
+ */
+module_list_type_t const module_list_type_global = {
+       .init = mlg_init,
+
+       .inst_size = sizeof(mlg_module_instance_t),
+       .data_add = mlg_data_add,
+       .data_del = mlg_data_del,
+
+       .thread = {
+               .init = mlg_thread_init,
+               .data_add = mlg_thread_data_add,
+               .data_get = mlg_thread_data_get,
+               .data_del = mlg_thread_data_del
+       }
+};
+
+/** A slightly larger module_instance structure to hold the module instance and thread instance
+ */
+typedef struct {
+       module_instance_t               mi;                     //!< Common module instance fields.  Must come first.
+       module_thread_instance_t        *ti;                    //!< Thread-specific data.  Still in its own structure
+                                                               ///< for talloc reasons.
+} mltl_module_instance_t;
+
+/** This causes the module_list code to skip bootstrapping for thread-local modules
+ */
+static int mltl_mlg_data_add(module_instance_t *mi)
+{
+       mi->state |= MODULE_INSTANCE_BOOTSTRAPPED;
+       return 0;
+}
+
+static void mltl_mlg_data_del(module_instance_t *mi)
+{
+       mltl_module_instance_t *mltl_mi = (mltl_module_instance_t *)talloc_get_type_abort(mi, module_instance_t);
+       module_thread_detach(mltl_mi->ti);
+}
+
+static module_thread_instance_t *mltl_thread_data_get(module_instance_t *mi)
+{
+       mltl_module_instance_t *mltl_mi = (mltl_module_instance_t *)talloc_get_type_abort(mi, module_instance_t);
+       return mltl_mi->ti;
+}
+
+static int mltl_thread_data_add(module_thread_instance_t *ti)
+{
+       mltl_module_instance_t *mltl_mi = (mltl_module_instance_t *)talloc_get_type_abort(ti->mi, module_instance_t);
+       mltl_mi->ti = ti;
+       return 0;
+}
+
+static void mltl_thread_data_del(module_thread_instance_t *ti)
+{
+       mltl_module_instance_t *mltl_mi = (mltl_module_instance_t *)talloc_get_type_abort(ti->mi, module_instance_t);
+       mltl_mi->ti = NULL;
+}
+
+/** Callbacks for a thread local list
+ */
+module_list_type_t const module_list_type_thread_local = {
+       .inst_size = sizeof(mltl_module_instance_t),
+
+       .data_add = mltl_mlg_data_add,
+       .data_del = mltl_mlg_data_del,
+
+       .thread = {
+               .data_add = mltl_thread_data_add,
+               .data_get = mltl_thread_data_get,
+               .data_del = mltl_thread_data_del
+       }
+};
 
 static int cmd_show_module_config(FILE *fp, UNUSED FILE *fp_err, void *ctx, UNUSED fr_cmd_info_t const *info);
 static int module_name_tab_expand(UNUSED TALLOC_CTX *talloc_ctx, UNUSED void *uctx, fr_cmd_info_t *info, int max_expansions, char const **expansions);
@@ -160,7 +524,7 @@ static int module_name_tab_expand(UNUSED TALLOC_CTX *talloc_ctx, UNUSED void *uc
        text = info->argv[info->argc - 1];
        count = 0;
 
-       fr_heap_foreach(module_global_inst_list, module_instance_t, instance) {
+       fr_heap_foreach(mlg_index, module_instance_t, instance) {
                module_instance_t       *mi = talloc_get_type_abort(instance, module_instance_t);
 
                if (count >= max_expansions) {
@@ -177,7 +541,7 @@ static int module_name_tab_expand(UNUSED TALLOC_CTX *talloc_ctx, UNUSED void *uc
 
 static int cmd_show_module_list(FILE *fp, UNUSED FILE *fp_err, UNUSED void *uctx, UNUSED fr_cmd_info_t const *info)
 {
-       fr_heap_foreach(module_global_inst_list, module_instance_t, instance) {
+       fr_heap_foreach(mlg_index, module_instance_t, instance) {
                module_instance_t *mi = talloc_get_type_abort(instance, module_instance_t);
 
                fprintf(fp, "\t%s\n", mi->name);
@@ -238,21 +602,6 @@ char const *module_instance_root_prefix_str(module_instance_t const *mi)
        return fr_table_str_by_value(dl_module_type_prefix, root->module->type, "<INVALID>");
 }
 
-/** Detach the shallowest parent first
- *
- */
-static void module_detach_parent(module_instance_t *mi)
-{
-       if (mi->detached) return;
-
-       if (mi->parent) module_detach_parent(UNCONST(module_instance_t *, mi->parent));
-
-       if (mi->exported->detach) {
-               mi->exported->detach(&(module_detach_ctx_t){ .mi = mi });
-               mi->detached = true;
-       }
-}
-
 /** Allocate module instance data
  *
  * @param[in] mi       to allocate instance data for
@@ -318,26 +667,6 @@ int module_instance_conf_parse(module_instance_t *mi, CONF_SECTION *conf)
        return 0;
 }
 
-/** Sort module instance data first by list then by number
- *
- * The module's position in the global instance heap informs of us
- * of its position in the thread-specific heap, which allows for
- * O(1) lookups.
- */
-static int8_t _module_instance_global_cmp(void const *one, void const *two)
-{
-       module_instance_t const *a = talloc_get_type_abort_const(one, module_instance_t);
-       module_instance_t const *b = talloc_get_type_abort_const(two, module_instance_t);
-       int8_t ret;
-
-       fr_assert(a->ml && b->ml);
-
-       ret = CMP(a->ml, b->ml);
-       if (ret != 0) return 0;
-
-       return CMP(a->number, b->number);
-}
-
 /** Compare module instances by parent and name
  *
  * The reason why we need parent, is because we could have submodules with names
@@ -432,7 +761,7 @@ int module_submodule_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
         *      and will be appended to the parent's instance
         *      name.
         */
-       mi = module_instance_alloc(ml, module_instance_by_data(ml, parent), DL_MODULE_TYPE_SUBMODULE, name, name);
+       mi = module_instance_alloc(ml, module_instance_by_data(ml, parent), DL_MODULE_TYPE_SUBMODULE, name, name, 0);
        if (unlikely(mi == NULL)) {
                cf_log_err(submodule_cs, "Failed loading submodule");
                return -1;
@@ -527,29 +856,6 @@ module_instance_t *module_instance_by_data(module_list_t const *ml, void const *
        return talloc_get_type_abort(mi, module_instance_t);
 }
 
-/** Retrieve module/thread specific instance for a module
- *
- * @param[in] mi       to find thread specific data for.
- * @return
- *     - Thread specific instance data on success.
- *     - NULL if module has no thread instance data.
- */
-module_thread_instance_t *module_thread(module_instance_t *mi)
-{
-       module_thread_instance_t         *ti;
-
-       fr_assert(mi->number < talloc_array_length(module_thread_inst_list));
-       fr_assert(module_list_in_sync);
-       fr_assert_msg(fr_heap_num_elements(module_global_inst_list) == talloc_array_length(module_thread_inst_list),
-                     "mismatch between global module heap (%u entries) and thread local (%zu entries)",
-                     fr_heap_num_elements(module_global_inst_list), talloc_array_length(module_thread_inst_list));
-
-       ti = talloc_get_type_abort(module_thread_inst_list[mi->inst_idx - 1], module_thread_instance_t);
-       fr_assert_msg(ti->mi == mi, "thread/module mismatch thread %s (%p), module %s (%p)",
-                     ti->mi->name, ti->mi, mi->name, mi);
-       return ti;
-}
-
 /** Retrieve module/thread specific instance data for a module
  *
  * @param[in] ml       Module list module belongs to.
@@ -563,58 +869,66 @@ module_thread_instance_t *module_thread(module_instance_t *mi)
 module_thread_instance_t *module_thread_by_data(module_list_t const *ml, void const *data)
 {
        module_instance_t               *mi = module_instance_by_data(ml, data);
-       module_thread_instance_t        *ti;
-       if (!mi) return NULL;
 
-       fr_assert(mi->number < ml->last_number);
-       fr_assert(module_list_in_sync);
-       fr_assert_msg(fr_heap_num_elements(module_global_inst_list) == talloc_array_length(module_thread_inst_list),
-                     "mismatch between global module heap (%u entries) and thread local (%zu entries)",
-                     fr_heap_num_elements(module_global_inst_list), talloc_array_length(module_thread_inst_list));
+       if (!mi) return NULL;
 
-       ti = talloc_get_type_abort(module_thread_inst_list[mi->inst_idx - 1], module_thread_instance_t);
-       fr_assert_msg(ti->mi == mi, "thread/module mismatch thread %s (%p), module %s (%p)",
-                     ti->mi->name, ti->mi, mi->name, mi);
-       return ti;
+       return module_thread(mi);
 }
 
-/** Explicitly free a module if a fatal error occurs during bootstrap
- *
- * @param[in] mi       to free.
- */
-void module_free(module_instance_t *mi)
+static void module_thread_detach(module_thread_instance_t *ti)
 {
-       talloc_free(mi);
+       module_list_t *ml = ti->mi->ml;
+       ml->type->thread.data_del(ti);
+       talloc_free(ti);
 }
 
 /** Remove thread-specific data for a given module list
  *
  * Removes all module thread data for the
  */
-void modules_thread_detach(module_list_t const *ml)
+void modules_thread_detach(module_list_t *ml)
 {
        fr_rb_iter_inorder_t            iter;
-       void                            *instance;
+       void                            *inst;
 
        /*
         *      Loop over all the modules in the module list
         *      finding and extracting their thread specific
         *      data, and calling their detach methods.
         */
-       for (instance = fr_rb_iter_init_inorder(&iter, ml->name_tree);
-            instance;
-            instance = fr_rb_iter_next_inorder(&iter)) {
-               module_instance_t               *mi = talloc_get_type_abort(instance, module_instance_t);
+       for (inst = fr_rb_iter_init_inorder(&iter, ml->name_tree);
+            inst;
+            inst = fr_rb_iter_next_inorder(&iter)) {
+               module_instance_t               *mi = talloc_get_type_abort(inst, module_instance_t);
+               module_thread_instance_t        *ti = module_thread(mi);
 
-               talloc_free(module_thread_inst_list[mi->inst_idx - 1]);
+               module_thread_detach(ti);
        }
+
+       /*
+        *      Cleanup any lists the module list added to this thread
+        */
+       if (ml->type->thread.free) ml->type->thread.free(ml);
 }
 
+/** Callback to free thread local data
+ *
+ * ti->data is allocated in the context of ti, so will be freed too.
+ *
+ * Calls the detach function for thread local data, and removes the data from the
+ * thread local list.
+ *
+ * @param[in] ti       to free.
+ */
 static int _module_thread_inst_free(module_thread_instance_t *ti)
 {
        module_instance_t const *mi = ti->mi;
 
-       module_list_in_sync = false;    /* Help catch anything attempting to do lookups */
+       /*
+        *      Never allocated a thread instance, so we don't need
+        *      to clean it up...
+        */
+       if (mi->state & MODULE_INSTANCE_NO_THREAD_INSTANCE) return 0;
 
        DEBUG4("Worker cleaning up %s thread instance data (%p/%p)",
               mi->exported->name, ti, ti->data);
@@ -627,33 +941,9 @@ static int _module_thread_inst_free(module_thread_instance_t *ti)
                                          });
        }
 
-       /*
-        *      Pull the thread instance out of the tree
-        */
-       module_thread_inst_list[ti->mi->inst_idx - 1] = NULL;
-       return 0;
-}
-
-/** Free the thread local heap on exit
- *
- * All thread local module lists should have been destroyed by this point
- */
-static int _module_thread_inst_list_free(void *tilp)
-{
-       module_thread_instance_t **til = talloc_get_type_abort(tilp, module_thread_instance_t *);
-       size_t i, len = talloc_array_length(til);
-       unsigned int found = 0;
-
-       for (i = 0; i < len; i++) if (til[i]) found++;
-
-
-       if (!fr_cond_assert_msg(found == 0,
-                               "Thread local array has %u non-null elements remaining on exit.  This is a leak",
-                               found)) {
-               return -1;
-       }
+       ti->mi->ml->type->thread.data_del(ti);
 
-       return talloc_free(til);
+       return 0;
 }
 
 /** Allocate thread-local instance data for a module
@@ -664,35 +954,41 @@ static int _module_thread_inst_list_free(void *tilp)
  * a specific dynamic use of that module.
  *
  * @param[in] ctx      Talloc ctx to bind thread specific data to.
- * @param[out] out     Where to write the allocated #module_thread_instance_t.
- * @param[in] ml       Module list to perform thread instantiation for.
  * @param[in] mi       Module instance to perform thread instantiation for.
  * @param[in] el       Event list serviced by this thread.
  * @return
  *     - 0 on success.
  *     - -1 on failure.
  */
-static int module_thread_instantiate(TALLOC_CTX *ctx, module_thread_instance_t **out, module_list_t const *ml,
-                                    module_instance_t *mi, fr_event_list_t *el)
+int module_thread_instantiate(TALLOC_CTX *ctx, module_instance_t *mi, fr_event_list_t *el)
 {
+       module_list_t                   *ml = mi->ml;
        module_thread_instance_t        *ti;
-       TALLOC_CTX                      *our_ctx = ctx;
+
+       /*
+        *      Allows the caller of module_instance_alloc to
+        *      skip thread instantiation for certain modules instances
+        *      whilst allowing modules to still register thread
+        *      instantiation callbacks.
+        *
+        *      This is mainly there for the single global instance of
+        *      a module, which will only have run-time thread-specific
+        *      instances, like dynamic/keyed modules.
+        */
+       if (mi->state & MODULE_INSTANCE_NO_THREAD_INSTANCE) return 0;
 
        /*
         *      Check the list pointers are ok
         */
        (void)talloc_get_type_abort(mi->ml, module_list_t);
 
-       MEM(ti = talloc_zero(our_ctx, module_thread_instance_t));
+       MEM(ti = talloc_zero(ctx, module_thread_instance_t));
        talloc_set_destructor(ti, _module_thread_inst_free);
        ti->el = el;
        ti->mi = mi;
 
        if (mi->exported->thread_inst_size) {
-               module_instance_t *rmi;
-
                MEM(ti->data = talloc_zero_array(ti, uint8_t, mi->exported->thread_inst_size));
-               rmi = module_instance_root(mi);
 
                /*
                 *      Fixup the type name, in case something calls
@@ -700,16 +996,21 @@ static int module_thread_instantiate(TALLOC_CTX *ctx, module_thread_instance_t *
                 */
                if (!mi->exported->thread_inst_type) {
                        talloc_set_name(ti->data, "%s_%s_thread_t",
-                                       fr_table_str_by_value(dl_module_type_prefix,
-                                                               rmi ? rmi->module->type :
-                                                                       mi->module->type,
-                                                               "<INVALID>"),
+                                       module_instance_root_prefix_str(mi),
                                        mi->exported->name);
                } else {
                        talloc_set_name_const(ti->data, mi->exported->thread_inst_type);
                }
        }
 
+       if (ml->type->thread.data_add(ti) < 0) {
+               PERROR("Failed adding thread data for module \"%s\"", mi->name);
+       error:
+               ml->type->thread.data_del(ti);
+               talloc_free(ti);
+               return -1;
+       }
+
        /*
         *      So we don't get spurious errors
         */
@@ -717,13 +1018,10 @@ static int module_thread_instantiate(TALLOC_CTX *ctx, module_thread_instance_t *
 
        DEBUG4("Alloced %s thread instance data (%p/%p)", ti->mi->exported->name, ti, ti->data);
        if (mi->exported->thread_instantiate &&
-               mi->exported->thread_instantiate(MODULE_THREAD_INST_CTX(mi, ti->data, el)) < 0) {
+           mi->exported->thread_instantiate(MODULE_THREAD_INST_CTX(mi, ti->data, el)) < 0) {
                PERROR("Thread instantiation failed for module \"%s\"", mi->name);
-               /* Leave module_thread_inst_list intact, other modules may need to clean up */
-               modules_thread_detach(ml);
-               return -1;
+               goto error;
        }
-       *out = ti;
 
        return 0;
 }
@@ -741,31 +1039,27 @@ static int module_thread_instantiate(TALLOC_CTX *ctx, module_thread_instance_t *
  */
 int modules_thread_instantiate(TALLOC_CTX *ctx, module_list_t const *ml, fr_event_list_t *el)
 {
-       void                    *instance;
+       void                    *inst;
        fr_rb_iter_inorder_t    iter;
+       int ret;
 
        /*
-        *      Initialise the thread specific tree if this is the
-        *      first time through or if everything else was
-        *      de-initialised.
+        *      Do any thread-local instantiation necessary
         */
-       if (!module_thread_inst_list) {
-               module_thread_instance_t **arr;
-
-               arr = talloc_zero_array(NULL, module_thread_instance_t *,
-                                       fr_heap_num_elements(module_global_inst_list));
-
-               fr_atexit_thread_local(module_thread_inst_list, _module_thread_inst_list_free, arr);
+       if (ml->type->thread.init) {
+               ret = ml->type->thread.init(&ctx, ml);
+               if (unlikely(ret < 0)) return ret;
        }
 
-       for (instance = fr_rb_iter_init_inorder(&iter, ml->name_tree);
-            instance;
-            instance = fr_rb_iter_next_inorder(&iter)) {
-               module_instance_t               *mi = talloc_get_type_abort(instance, module_instance_t);
-               module_thread_instance_t        *ti;
+       for (inst = fr_rb_iter_init_inorder(&iter, ml->name_tree);
+            inst;
+            inst = fr_rb_iter_next_inorder(&iter)) {
+               module_instance_t               *mi = talloc_get_type_abort(inst, module_instance_t); /* Sanity check*/
 
-               if (module_thread_instantiate(ctx, &ti, ml, mi, el) < 0) return -1;
-               module_thread_inst_list[ti->mi->inst_idx - 1] = ti;
+               if (module_thread_instantiate(ctx, mi, el) < 0) {
+                       modules_thread_detach(UNCONST(module_list_t *, ml));
+                       return -1;
+               }
        }
 
        return 0;
@@ -786,7 +1080,8 @@ int module_instantiate(module_instance_t *instance)
        /*
         *      We only instantiate modules in the bootstrapped state
         */
-       if (mi->state != MODULE_INSTANCE_BOOTSTRAPPED) return 0;
+       if ((!fr_cond_assert(mi->state & MODULE_INSTANCE_BOOTSTRAPPED)) ||
+           (mi->state & MODULE_INSTANCE_INSTANTIATED)) return 0;
 
        if (mi->module->type == DL_MODULE_TYPE_MODULE) {
                if (fr_command_register_hook(NULL, mi->name, mi, module_cmd_table) < 0) {
@@ -820,7 +1115,7 @@ int module_instantiate(module_instance_t *instance)
                        return -1;
                }
        }
-       mi->state = MODULE_INSTANCE_INSTANTIATED;
+       mi->state |= MODULE_INSTANCE_INSTANTIATED;
 
        return 0;
 }
@@ -846,8 +1141,6 @@ int modules_instantiate(module_list_t const *ml)
             instance;
             instance = fr_rb_iter_next_inorder(&iter)) {
                module_instance_t *mi = talloc_get_type_abort(instance, module_instance_t);
-               if (mi->state != MODULE_INSTANCE_BOOTSTRAPPED) continue;
-
                if (module_instantiate(mi) < 0) return -1;
        }
 
@@ -869,7 +1162,7 @@ int module_bootstrap(module_instance_t *mi)
        /*
         *      We only bootstrap modules in the init state
         */
-       if (mi->state != MODULE_INSTANCE_INIT) return 0;
+       if (mi->state & MODULE_INSTANCE_BOOTSTRAPPED) return 0;
 
        /*
         *      Bootstrap the module.
@@ -891,7 +1184,7 @@ int module_bootstrap(module_instance_t *mi)
                        return -1;
                }
        }
-       mi->state = MODULE_INSTANCE_BOOTSTRAPPED;
+       mi->state |= MODULE_INSTANCE_BOOTSTRAPPED;
 
        return 0;
 }
@@ -917,8 +1210,6 @@ int modules_bootstrap(module_list_t const *ml)
             instance;
             instance = fr_rb_iter_next_inorder(&iter)) {
                module_instance_t *mi = talloc_get_type_abort(instance, module_instance_t);
-               if (mi->state != MODULE_INSTANCE_INIT) continue;
-
                if (module_bootstrap(mi) < 0) return -1;
        }
 
@@ -936,15 +1227,17 @@ static fr_slen_t module_instance_name(TALLOC_CTX *ctx, char **out,
                                      module_instance_t const *parent, char const *inst_name)
 {
        fr_sbuff_t *agg;
-       module_instance_t const *mi;
 
        FR_SBUFF_TALLOC_THREAD_LOCAL(&agg, 64, 256);
 
-       for (mi = parent; mi; mi = mi->parent) {
-               FR_SBUFF_IN_STRCPY_RETURN(agg, mi->name);
+       /*
+        *      Parent has all of the qualifiers of its ancestors
+        *      already in the name, so we just need to concatenate.
+        */
+       if (parent) {
+               FR_SBUFF_IN_STRCPY_RETURN(agg, parent->name);
                FR_SBUFF_IN_CHAR_RETURN(agg, '.');
        }
-
        FR_SBUFF_IN_STRCPY_RETURN(agg, inst_name);
 
        MEM(*out = talloc_bstrndup(ctx, fr_sbuff_start(agg), fr_sbuff_used(agg)));
@@ -952,6 +1245,33 @@ static fr_slen_t module_instance_name(TALLOC_CTX *ctx, char **out,
        return fr_sbuff_used(agg);
 }
 
+/** Detach the shallowest parent first
+ *
+ * This ensures that the module's parent is detached before it is.
+ *
+ * Generally parents reach into their children and not the other way
+ * around.  Calling the parent's detach method first ensures that
+ * there's no code that access the child module's instance data or
+ * reach into its symbol space if it's being unloaded.
+ *
+ * @note If you don't want to detach the parent, maybe because its children
+ *     are ephemeral, consider using a seaprate thread-local module list
+ *     to hold the children instead.
+ *
+ * @param[in] mi       to detach.
+ */
+static void module_detach_parent(module_instance_t *mi)
+{
+       if (mi->detached) return;
+
+       if (mi->parent) module_detach_parent(UNCONST(module_instance_t *, mi->parent));
+
+       if (mi->exported && mi->exported->detach) {
+               mi->exported->detach(&(module_detach_ctx_t){ .mi = mi });
+               mi->detached = true;
+       }
+}
+
 /** Free module's instance data, and any xlats or paircmps
  *
  * @param[in] mi to free.
@@ -963,9 +1283,9 @@ static int _module_instance_free(module_instance_t *mi)
 
        DEBUG3("Freeing %s (%p)", mi->name, mi);
 
-       if (fr_heap_entry_inserted(mi->inst_idx) && !fr_cond_assert(fr_heap_extract(&module_global_inst_list, mi) == 0)) return 1;
        if (fr_rb_node_inline_in_tree(&mi->name_node) && !fr_cond_assert(fr_rb_delete(ml->name_tree, mi))) return 1;
        if (fr_rb_node_inline_in_tree(&mi->data_node) && !fr_cond_assert(fr_rb_delete(ml->data_tree, mi))) return 1;
+       if (ml->type->data_del) ml->type->data_del(mi);
 
        /*
         *      mi->exported may be NULL if we failed loading the module
@@ -991,7 +1311,7 @@ static int _module_instance_free(module_instance_t *mi)
        /*
         *      Remove all xlat's registered to module instance.
         */
-       if (mi && mi->data) {
+       if (mi->data) {
                xlat_func_unregister(mi->name);
                xlat_func_unregister_module(mi);
        }
@@ -1013,24 +1333,44 @@ static int _module_instance_free(module_instance_t *mi)
        return 0;
 }
 
+/** Duplicate a module instance, placing it in a new module list
+ *
+ * @param[in] dst      list to place the new module instance in.
+ * @param[in] src      to duplicate.
+ * @param[in] inst_name        new instance name.  If null, src->name will be used.
+ */
+module_instance_t *module_instance_copy(module_list_t *dst, module_instance_t const *src, char const *inst_name)
+{
+       module_instance_t *mi = module_instance_alloc(dst, src->parent, src->module->type,
+                                                     src->module->name,
+                                                     inst_name ? inst_name : src->name, 0);
+       if (!mi) return NULL;
+
+       return mi;
+}
+
 /** Allocate a new module and add it to a module list for later bootstrap/instantiation
  *
  * - Load the module shared library.
  * - Allocate instance data for it.
  *
- * @param[in] ml       To add module to.
- * @param[in] parent   of the module being bootstrapped, if this is a submodule.
- *                     If this is not a submodule parent must be NULL.
- * @param[in] type     What type of module we're loading.  Determines the prefix
- *                     added to the library name.  Should be one of:
- *                     - DL_MODULE_TYPE_MODULE - Standard backend module.
- *                     - DL_MODULE_TYPE_SUBMODULE - Usually a driver for a backend module.
- *                     - DL_MODULE_TYPE_PROTO - A module associated with a listen section.
- *                     - DL_MODULE_TYPE_PROCESS - Protocol state machine bound to a virtual server.
- * @param[in] mod_name The name of this module, i.e. 'redis' for 'rlm_redis'.
- * @param[in] inst_name        Instance name for this module, i.e. "aws_redis_01".
- *                     The notable exception is if this is a submodule, in which case
- *                     inst_name is usually the mod_name.
+ * @param[in] ml               To add module to.
+ * @param[in] parent           of the module being bootstrapped, if this is a submodule.
+ *                             If this is not a submodule parent must be NULL.
+ * @param[in] type             What type of module we're loading.  Determines the prefix
+ *                             added to the library name.  Should be one of:
+ *                             - DL_MODULE_TYPE_MODULE - Standard backend module.
+ *                             - DL_MODULE_TYPE_SUBMODULE - Usually a driver for a backend module.
+ *                             - DL_MODULE_TYPE_PROTO - A module associated with a listen section.
+ *                             - DL_MODULE_TYPE_PROCESS - Protocol state machine bound to a virtual server.
+ * @param[in] mod_name         The name of this module, i.e. 'redis' for 'rlm_redis'.
+ * @param[in] inst_name                Instance name for this module, i.e. "aws_redis_01".
+ *                             The notable exception is if this is a submodule, in which case
+ *                             inst_name is usually the mod_name.
+ * @param[in] init_state       The state the module "starts" in.  Can be used to prevent
+ *                             bootstrapping, instantiation, or thread instantiation of the module,
+ *                             by passing one or more of the MODULE_INSTANCE_* flags.
+ *                             Should usually be 0, unless special behaviour is required.
  * @return
  *     - A new module instance handle, containing the module's public interface,
  *       and private instance data.
@@ -1038,7 +1378,8 @@ static int _module_instance_free(module_instance_t *mi)
  */
 module_instance_t *module_instance_alloc(module_list_t *ml,
                                         module_instance_t const *parent,
-                                        dl_module_type_t type, char const *mod_name, char const *inst_name)
+                                        dl_module_type_t type, char const *mod_name, char const *inst_name,
+                                        module_instance_state_t init_state)
 {
        char                    *qual_inst_name = NULL;
        module_instance_t       *mi;
@@ -1084,12 +1425,18 @@ module_instance_t *module_instance_alloc(module_list_t *ml,
                return NULL;
        }
 
-       MEM(mi = talloc_zero(parent ? (void const *)parent : (void const *)ml, module_instance_t));
+       /*
+        *      Overallocate the module instance, so we can add
+        *      some module list type specific data to it.
+        */
+       MEM(mi = (module_instance_t *)talloc_zero_array(parent ? (void const *)parent : (void const *)ml, uint8_t, ml->type->inst_size));
+       talloc_set_name_const(mi, "module_instance_t");
        mi->name = talloc_typed_strdup(mi, qual_inst_name);
        talloc_free(qual_inst_name);    /* Avoid stealing */
 
        mi->ml = ml;
        mi->parent = parent;
+       mi->state = init_state;
 
        /*
         *      Increment the reference count on an already loaded module,
@@ -1126,25 +1473,15 @@ module_instance_t *module_instance_alloc(module_list_t *ml,
         */
        if ((mi->exported->flags & MODULE_TYPE_THREAD_UNSAFE) != 0) pthread_mutex_init(&mi->mutex, NULL);
        talloc_set_destructor(mi, _module_instance_free);       /* Set late intentionally */
-
-       mi->number = ml->last_number++;
        mi->ml = ml;
+       mi->number = ml->last_number++;
 
        /*
         *      Remember the module for later.
         */
        if (!fr_cond_assert(fr_rb_insert(ml->name_tree, mi))) goto error;
        if (!fr_cond_assert(fr_rb_insert(ml->data_tree, mi))) goto error;
-
-       /*
-        *      ...and finally insert the module
-        *      into the global heap so we can
-        *      get common thread-local indexes.
-        */
-       if (fr_heap_insert(&module_global_inst_list, mi) < 0) {
-               ERROR("Failed inserting into global module index");
-               goto error;
-       }
+       if (ml->type->data_add && unlikely(ml->type->data_add(mi) < 0)) goto error;
 
        return mi;
 }
@@ -1170,6 +1507,8 @@ static int _module_list_free(module_list_t *ml)
                talloc_free(mi);
        }
 
+       if (ml->type->free) ml->type->free(ml);
+
        return 0;
 }
 
@@ -1182,27 +1521,43 @@ static int _module_list_free(module_list_t *ml)
  * If no more instances of the module exist the module be unloaded.
  *
  * @param[in] ctx      To allocate the list in.
- * @param[in] name     of the list.
+ * @param[in] type     of the list.  Controls whether this is a global
+ *                     module list, or a per-thread list containing
+ *                     variants of an existing module.
+ * @param[in] name     of the list.  Used for debugging.
  * @return A new module list.
  */
-module_list_t *module_list_alloc(TALLOC_CTX *ctx, char const *name)
+module_list_t *module_list_alloc(TALLOC_CTX *ctx, module_list_type_t const *type, char const *name)
 {
        module_list_t *ml;
 
+       /*
+        *      These callbacks are NOT optional, the rest are.
+        */
+       fr_assert(type->thread.data_add);
+       fr_assert(type->thread.data_get);
+       fr_assert(type->thread.data_del);
+
        MEM(ml = talloc_zero(ctx, module_list_t));
-       talloc_set_destructor(ml, _module_list_free);
+       ml->type = type;
 
+       ml->thread_data_get = type->thread.data_get;    /* Cache for access outside of the compilation unit */
        MEM(ml->name = talloc_typed_strdup(ml, name));
        MEM(ml->name_tree = fr_rb_inline_alloc(ml, module_instance_t, name_node, module_instance_name_cmp, NULL));
        MEM(ml->data_tree = fr_rb_inline_alloc(ml, module_instance_t, data_node, module_instance_data_cmp, NULL));
+       talloc_set_destructor(ml, _module_list_free);
+
+       if (ml->type->init && (ml->type->init(ml) < 0)) {
+               talloc_free(ml);
+               return NULL;
+       }
 
        return ml;
 }
 
-static int _module_global_list_init(void *uctx)
+static int _module_dl_loader_init(void *uctx)
 {
        dl_modules = dl_module_loader_init(uctx);
-       MEM(module_global_inst_list = fr_heap_alloc(NULL, _module_instance_global_cmp, module_instance_t, inst_idx, 256));
 
        /*
         *      Ensure the common library tracking
@@ -1213,15 +1568,8 @@ static int _module_global_list_init(void *uctx)
        return 0;
 }
 
-static int _module_global_list_free(UNUSED void *uctx)
+static int _module_dl_loader_free(UNUSED void *uctx)
 {
-
-       if (!fr_cond_assert_msg(fr_heap_num_elements(module_global_inst_list) == 0,
-                               "Global module heap has %u elements remaining on exit.  This is a leak",
-                               fr_heap_num_elements(module_global_inst_list))) return -1;
-       if (talloc_free(module_global_inst_list) < 0) return -1;
-       module_global_inst_list = NULL;
-
        if (talloc_free(dl_modules) < 0) return -1;
        dl_modules = NULL;
        return 0;
@@ -1237,5 +1585,5 @@ void modules_init(char const *lib_dir)
         *      common indexes in the thread-specific
         *      heaps.
         */
-       fr_atexit_global_once(_module_global_list_init, _module_global_list_free, UNCONST(char *, lib_dir));
+       fr_atexit_global_once(_module_dl_loader_init, _module_dl_loader_free, UNCONST(char *, lib_dir));
 }
index f3448202e6e2416a3ec7dbd5efc2a163fb8e1daf..ae4e4be5c7e0fe4033a9363687c5434e79794fae 100644 (file)
@@ -42,7 +42,8 @@ typedef struct module_s                               module_t;
 typedef struct module_method_name_s            module_method_name_t;
 typedef struct module_instance_s               module_instance_t;
 typedef struct module_thread_instance_s                module_thread_instance_t;
-typedef struct module_list_t                   module_list_t;
+typedef struct module_list_type_s              module_list_type_t;
+typedef struct module_list_s                   module_list_t;
 
 DIAG_OFF(attributes)
 typedef enum CC_HINT(flag_enum) {
@@ -144,7 +145,7 @@ struct module_method_name_s {
        char const                      *name2;                 //!< The packet type i.e Access-Request, Access-Reject.
 
        module_method_t                 method;                 //!< Module method to call
-       call_env_method_t const         *method_env;            //!< Call specific conf parsing.
+       call_env_method_t const * const method_env;             //!< Call specific conf parsing.
 };
 
 #define MODULE_NAME_TERMINATOR { NULL }
@@ -184,14 +185,16 @@ struct module_s {
 /** What state the module instance is currently in
  *
  */
-typedef enum {
-       MODULE_INSTANCE_INIT = 0,                       //!< Module instance has been allocated, but not
-                                                       ///< yet bootstrapped.
-       MODULE_INSTANCE_BOOTSTRAPPED,                   //!< Module instance has been bootstrapped, but not
-                                                       ///< yet instantiated.
-       MODULE_INSTANCE_INSTANTIATED                    //!< Module instance has been bootstrapped and
-                                                       ///< instantiated.
+DIAG_OFF(attributes)
+typedef enum CC_HINT(flag_enum) {
+       MODULE_INSTANCE_BOOTSTRAPPED            = (1 << 1),     //!< Module instance has been bootstrapped, but not
+                                                               ///< yet instantiated.
+       MODULE_INSTANCE_INSTANTIATED            = (1 << 2),     //!< Module instance has been bootstrapped and
+                                                               ///< instantiated.
+       MODULE_INSTANCE_NO_THREAD_INSTANCE      = (1 << 3)      //!< Not set internally, but can be used to prevent
+                                                               ///< thread instantiation for certain modules.
 } module_instance_state_t;
+DIAG_ON(attributes)
 
 /** Per instance data
  *
@@ -200,9 +203,6 @@ typedef enum {
  * data structures.
  */
 struct module_instance_s {
-       fr_heap_index_t                 inst_idx;       //!< Entry in the bootstrap/instantiation heap.
-                                                       //!< should be an identical value to the thread-specific
-                                                       ///< data for this module.
        module_instance_state_t         state;          //!< What's been done with this module so far.
 
        fr_rb_node_t                    name_node;      //!< Entry in the name tree.
@@ -210,9 +210,6 @@ struct module_instance_s {
 
        module_list_t                   *ml;            //!< Module list this instance belongs to.
 
-       uint32_t                        number;         //!< Unique module number.  This is used to access the
-                                                       ///< thread-specific module instance.
-
        char const                      *name;          //!< Instance name e.g. user_database.
 
        dl_module_t                     *module;        //!< dynamic loader handle.  Contains the module's
@@ -236,6 +233,10 @@ struct module_instance_s {
 
        pthread_mutex_t                 mutex;          //!< Used prevent multiple threads entering a thread
                                                        ///< unsafe module simultaneously.
+
+       uint32_t                        number;         //!< Unique module number.  Used to assign a stable
+                                                       ///< number to each module instance.
+
        /** @name Return code overrides
         * @{
         */
@@ -271,32 +272,60 @@ struct module_thread_instance_s {
        uint64_t                        active_callers; //! number of active callers.  i.e. number of current yields
 };
 
+/** Callback to retrieve thread-local data for a module
+ *
+ * @param[in] mi       to add data to (use mi->ml for the module list).
+ * @return
+ *     - NULL if no data exists.
+ *     - Pointer to the data on success.
+ */
+typedef module_thread_instance_t *(*module_list_thread_data_get_t)(module_instance_t *mi);
+
 /** A list of modules
  *
  * This allows modules to be instantiated and freed in phases,
  * i.e. proto modules before rlm modules.
  */
-struct module_list_t {
-       uint32_t                        last_number;    //!< Last identifier assigned to a module instance.
-       char const                      *name;          //!< Friendly list identifier.
-       fr_rb_tree_t                    *name_tree;     //!< Modules indexed by name.
-       fr_rb_tree_t                    *data_tree;     //!< Modules indexed by data.
+struct module_list_s {
+       uint32_t                        last_number;            //!< Last identifier assigned to a module instance.
+       char const                      *name;                  //!< Friendly list identifier.
+       fr_rb_tree_t                    *name_tree;             //!< Modules indexed by name.
+       fr_rb_tree_t                    *data_tree;             //!< Modules indexed by data.
+       fr_heap_t                       *inst_heap;             //!< Heap of module instances.
+
+       /** @name Callbacks to manage thread-specific data
+        *
+        * In "child" lists, which are only operating in a single thread, we don't need
+        * to use true thread-local data, because the module list itself is thread-local.
+        *
+        * In that case these callbacks hang memory off of the list itself.
+        *
+        * In the main module list, which is shared between threads, these callbacks
+        * do use true thread-local data, to manage the module_thread_instance_t
+        * on a per thread-basis.
+        *
+        * @{
+        */
+       module_list_type_t const        *type;                  //!< Type of module list.
+       module_list_thread_data_get_t   thread_data_get;        //!< Callback to get thread-specific data.
+                                                               ///< Copy of type->thread_data_get.
+       /** @} */
 };
 
 /** Map string values to module state method
  *
  */
 typedef struct {
-       char const                      *name;          //!< String identifier for state.
-       module_method_t                 func;           //!< State function.
+       char const                      *name;                  //!< String identifier for state.
+       module_method_t                 func;                   //!< State function.
 } module_state_func_table_t;
 
 /** @name Callbacks for the conf_parser_t
  *
  * @{
  */
-int            module_submodule_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
-                                      CONF_ITEM *ci, UNUSED conf_parser_t const *rule) CC_HINT(warn_unused_result);
+int                    module_submodule_parse(UNUSED TALLOC_CTX *ctx, void *out, void *parent,
+                                              CONF_ITEM *ci, UNUSED conf_parser_t const *rule) CC_HINT(warn_unused_result);
 /** @} */
 
 /** @name Module and module thread lookup
@@ -316,7 +345,18 @@ module_instance_t  *module_instance_by_name(module_list_t const *ml, module_insta
 
 module_instance_t      *module_instance_by_data(module_list_t const *ml, void const *data) CC_HINT(warn_unused_result);
 
-module_thread_instance_t *module_thread(module_instance_t *mi) CC_HINT(warn_unused_result);
+/** Retrieve module/thread specific instance for a module
+ *
+ * @param[in] mi       to find thread specific data for.
+ * @return
+ *     - Thread specific instance data on success.
+ *     - NULL if module has no thread instance data.
+ */
+static inline CC_HINT(warn_unused_result) CC_HINT(always_inline)
+module_thread_instance_t *module_thread(module_instance_t *mi)
+{
+       return mi->ml->thread_data_get(mi);
+}
 
 module_thread_instance_t *module_thread_by_data(module_list_t const *ml, void const *data) CC_HINT(warn_unused_result);
 /** @} */
@@ -325,28 +365,51 @@ module_thread_instance_t *module_thread_by_data(module_list_t const *ml, void co
  *
  * @{
  */
-void           module_free(module_instance_t *mi);
+void                   modules_thread_detach(module_list_t *ml);
+
+int                    module_thread_instantiate(TALLOC_CTX *ctx, module_instance_t *mi, fr_event_list_t *el);
+                       CC_HINT(nonnull) CC_HINT(warn_unused_result);
 
-void           modules_thread_detach(module_list_t const *ml);
+int                    modules_thread_instantiate(TALLOC_CTX *ctx, module_list_t const *ml, fr_event_list_t *el)
+                       CC_HINT(nonnull) CC_HINT(warn_unused_result);
 
-int            modules_thread_instantiate(TALLOC_CTX *ctx, module_list_t const *ml, fr_event_list_t *el) CC_HINT(nonnull) CC_HINT(warn_unused_result);
+int                    module_instantiate(module_instance_t *mi) CC_HINT(nonnull) CC_HINT(warn_unused_result);
 
-int            module_instantiate(module_instance_t *mi) CC_HINT(nonnull) CC_HINT(warn_unused_result);
+int                    modules_instantiate(module_list_t const *ml) CC_HINT(nonnull) CC_HINT(warn_unused_result);
 
-int            modules_instantiate(module_list_t const *ml) CC_HINT(nonnull) CC_HINT(warn_unused_result);
+int                    module_bootstrap(module_instance_t *mi) CC_HINT(nonnull) CC_HINT(warn_unused_result);
 
-int            module_bootstrap(module_instance_t *mi) CC_HINT(nonnull) CC_HINT(warn_unused_result);
+int                    modules_bootstrap(module_list_t const *ml) CC_HINT(nonnull) CC_HINT(warn_unused_result);
 
-int            modules_bootstrap(module_list_t const *ml) CC_HINT(nonnull) CC_HINT(warn_unused_result);
+module_instance_t      *module_instance_copy(module_list_t *dst, module_instance_t const *src, char const *inst_name)
+                       CC_HINT(nonnull(1,2)) CC_HINT(warn_unused_result);
 
-module_instance_t *module_instance_alloc(module_list_t *ml,
-                               module_instance_t const *parent,
-                               dl_module_type_t type, char const *mod_name, char const *inst_name)
-                               CC_HINT(nonnull(1)) CC_HINT(warn_unused_result);
+module_instance_t      *module_instance_alloc(module_list_t *ml,
+                                              module_instance_t const *parent,
+                                              dl_module_type_t type, char const *mod_name, char const *inst_name,
+                                              module_instance_state_t init_state)
+                                              CC_HINT(nonnull(1)) CC_HINT(warn_unused_result);
+
+/** @name Module list variants
+ *
+ * These are passed to the module_list_alloc function to allocate lists of different types
+ *
+ * Global module lists are used for backend modules, listeners, and process state machines.
+ *
+ * Thread-local lists are usually runtime instantiated variants of modules, or modules that represent client connections.
+ *
+ * One major difference (from the module's perspective) is that bootstrap is not called for thread-local modules.
+ *
+ * @{
+ */
+extern module_list_type_t const module_list_type_global;               //!< Initialise a global module, with thread-specific data.
+extern module_list_type_t const module_list_type_thread_local; //!< Initialise a thread-local module, which is only used in a single thread.
+/** @} */
 
-module_list_t  *module_list_alloc(TALLOC_CTX *ctx, char const *name) CC_HINT(warn_unused_result);
+module_list_t          *module_list_alloc(TALLOC_CTX *ctx, module_list_type_t const *type, char const *name)
+                                          CC_HINT(nonnull(2,3)) CC_HINT(warn_unused_result);
 
-void           modules_init(char const *lib_dir);
+void                   modules_init(char const *lib_dir);
 /** @} */
 
 #ifdef __cplusplus
index e9983a3555f1c327a52191928429c2cdf4a23025..a0f9efb9889f00a43b09f41ef3911f33c2d90529 100644 (file)
@@ -1039,7 +1039,7 @@ int modules_rlm_bootstrap(CONF_SECTION *root)
                        continue;
                }
 
-               mi = module_instance_alloc(rlm_modules, NULL, DL_MODULE_TYPE_MODULE, name, module_instance_name_from_conf(subcs));
+               mi = module_instance_alloc(rlm_modules, NULL, DL_MODULE_TYPE_MODULE, name, module_instance_name_from_conf(subcs), 0);
                if (unlikely(mi == NULL)) {
                        cf_log_perr(subcs, "Failed loading module");
                        return -1;
@@ -1157,7 +1157,7 @@ static int _modules_rlm_free_atexit(UNUSED void *uctx)
  */
 int modules_rlm_init(void)
 {
-       MEM(rlm_modules = module_list_alloc(NULL, "rlm"));
+       MEM(rlm_modules = module_list_alloc(NULL, &module_list_type_global, "rlm"));
        MEM(module_rlm_virtual_name_tree = fr_rb_inline_alloc(NULL, module_rlm_virtual_t, name_node,
                                                              module_rlm_virtual_name_cmp, NULL));
        fr_atexit_global(_modules_rlm_free_atexit, NULL);
index b1b056ae45943787fdc94f247184c7ffeb136b07..97b3cc1864985c823fe3e3c9381b1223623f36f7 100644 (file)
@@ -223,7 +223,8 @@ static int namespace_on_read(TALLOC_CTX *ctx, UNUSED void *out, UNUSED void *par
         *      The instance name is the virtual server name.
         */
        mi = module_instance_alloc(process_modules, NULL, DL_MODULE_TYPE_PROCESS,
-                         module_name, module_instance_name_from_conf(server_cs));
+                                  module_name, module_instance_name_from_conf(server_cs),
+                                  0);
        talloc_free(module_name);
        if (mi == NULL) {
        error:
@@ -444,7 +445,7 @@ static int listen_parse(UNUSED TALLOC_CTX *ctx, void *out, UNUSED void *parent,
        if (!inst_name) inst_name = mod_name;
 
        MEM(qual_inst_name = talloc_asprintf(NULL, "%s.%s", cf_section_name2(server_cs), inst_name));
-       mi = module_instance_alloc(proto_modules, NULL, DL_MODULE_TYPE_PROTO, mod_name, qual_inst_name);
+       mi = module_instance_alloc(proto_modules, NULL, DL_MODULE_TYPE_PROTO, mod_name, qual_inst_name, 0);
        talloc_free(qual_inst_name);
        if (!mi) {
        error:
@@ -1594,8 +1595,8 @@ int virtual_servers_init(void)
                return -1;
        }
 
-       MEM(process_modules = module_list_alloc(NULL, "process"));
-       MEM(proto_modules = module_list_alloc(NULL, "protocol"));
+       MEM(process_modules = module_list_alloc(NULL, &module_list_type_global, "process"));
+       MEM(proto_modules = module_list_alloc(NULL, &module_list_type_global, "protocol"));
        MEM(listen_addr_root = fr_rb_inline_alloc(NULL, fr_listen_t, virtual_server_node, listen_addr_cmp, NULL));
        MEM(server_section_name_tree = fr_rb_alloc(NULL, server_section_name_cmp, NULL));
 
index b4a1b4d6814c18d9374898e7cfd8207d49f735ed..41bc7c25ba9167d33eee66f70c03024cdf7f5fcf 100644 (file)
@@ -572,7 +572,7 @@ static int mod_bootstrap(module_inst_ctx_t const *mctx)
                 *      of that list.
                 */
                inst->work_submodule = module_instance_alloc(parent_inst->ml, parent_inst, DL_MODULE_TYPE_SUBMODULE,
-                                                            "work", module_instance_name_from_conf(transport_cs));
+                                                            "work", module_instance_name_from_conf(transport_cs), 0);
                if (inst->work_submodule == NULL) {
                error:
                        cf_log_perr(inst->cs, "Failed to load proto_detail_work");