On 6/9/2026 10:26 PM, Stephen Hemminger wrote:
> EAL_REGISTER_TAILQ registers a static rte_tailq_elem from a
> constructor but provides no destructor. If a library using the
> macro is loaded with dlopen() and later unloaded with dlclose(),
> the process-local list keeps a dangling pointer to the unmapped
> elem, and the next dlopen() crashes in rte_eal_tailq_local_register()
> while walking the list.
>
> Add a new RTE_FINI destructor that is paired with the constructor
> in the macro. rte_eal_tailq_unregister() drops the local entry on
> unload. The shared mcfg->tailq_head[] slot is left reserved since
> it is keyed by name and shared between processes;
> rte_eal_tailq_update() now reattaches to that slot on re-register
> instead of failing.
>
> Bugzilla ID: 1081
> Fixes: 873a61c7526b ("tailq: introduce dynamic register system")
> Cc: [email protected]
>
> Signed-off-by: Stephen Hemminger <[email protected]>
> Acked-by: Bruce Richardson <[email protected]>
> ---
> v2 - cover the case where name still is reserved
>
> lib/eal/common/eal_common_tailqs.c | 13 +++++++++++++
> lib/eal/include/rte_tailq.h | 16 ++++++++++++++++
> 2 files changed, 29 insertions(+)
>
> diff --git a/lib/eal/common/eal_common_tailqs.c
> b/lib/eal/common/eal_common_tailqs.c
> index c581f43b6f..9355c108f2 100644
> --- a/lib/eal/common/eal_common_tailqs.c
> +++ b/lib/eal/common/eal_common_tailqs.c
> @@ -113,6 +113,11 @@ rte_eal_tailq_update(struct rte_tailq_elem *t)
> if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
> /* primary process is the only one that creates */
> t->head = rte_eal_tailq_create(t->name);
> +
> + if (t->head == NULL) {
> + /* slot reserved by an earlier load -- reuse it */
> + t->head = rte_eal_tailq_lookup(t->name);
> + }
> } else {
> t->head = rte_eal_tailq_lookup(t->name);
> }
> @@ -148,6 +153,14 @@ rte_eal_tailq_register(struct rte_tailq_elem *t)
> return -1;
> }
>
> +RTE_EXPORT_SYMBOL(rte_eal_tailq_unregister)
this should be with EXPERIMENTAL
> +void
> +rte_eal_tailq_unregister(struct rte_tailq_elem *t)
> +{
> + TAILQ_REMOVE(&rte_tailq_elem_head, t, next);
We need first make sure it exist the tailq, just like TAILQ_FOREACH
rte_eal_tailq_local_register()
> + t->head = NULL;
> +}
> +
> int
> rte_eal_tailqs_init(void)
> {
> diff --git a/lib/eal/include/rte_tailq.h b/lib/eal/include/rte_tailq.h
> index e7caed6812..d4d8bfd6d4 100644
> --- a/lib/eal/include/rte_tailq.h
> +++ b/lib/eal/include/rte_tailq.h
> @@ -117,11 +117,27 @@ struct rte_tailq_head *rte_eal_tailq_lookup(const char
> *name);
> */
> int rte_eal_tailq_register(struct rte_tailq_elem *t);
>
> +/**
> + * Remove a tail queue element from the local list.
> + * This function is mainly used for EAL_REGISTER_TAILQ macro which pairs
> + * an RTE_FINI destructor with the existing RTE_INIT constructor.
> + * The destructor calls this function during dlclose() to prevent
> + * dangling pointers to unmapped library data.
Currently this patch only support next dlopen() the same so file. the
global still exist. Suggest add document for the global entry still exist,
just like commit-log:
"The shared mcfg->tailq_head[] slot is left reserved since
it is keyed by name and shared between processes;"
> + *
> + * @param t
> + * The tailq element to remove from the EAL tailq list.
> + */
> +void rte_eal_tailq_unregister(struct rte_tailq_elem *t);
> +
> #define EAL_REGISTER_TAILQ(t) \
> RTE_INIT(tailqinitfn_ ##t) \
> { \
> if (rte_eal_tailq_register(&t) < 0) \
> rte_panic("Cannot initialize tailq: %s\n", t.name); \
> +} \
> +RTE_FINI(tailqfinifn_ ##t) \
> +{ \
> + rte_eal_tailq_unregister(&t); \
> }
>
> /* This macro permits both remove and free var within the loop safely.*/