From: Robert Sanford <[email protected]>
rte_mempool_create_empty() adds a mempool to the mempool list before
the mempool has a store. The store is allocated, and
RTE_MEMPOOL_F_POOL_CREATED is set, by the first populate call. In
between, rte_mempool_dump() queries the mempool driver for the number
of objects in the store. With the ring driver this dereferences a NULL
ring, and the process crashes. rte_mempool_list_dump() calls
rte_mempool_dump() for every mempool on the list, so it crashes when
another thread has created a mempool but not populated it yet. The
telemetry /mempool/info handler has the same problem: its
rte_mempool_walk() callback queries the driver for the same count.
In rte_mempool_dump() and in the telemetry callback, query the mempool
driver only when RTE_MEMPOOL_F_POOL_CREATED is set, and report a common
pool count of 0 otherwise, since the mempool has no objects yet. The
rest of the mempool information is still reported. In statistics
builds, rte_mempool_dump() also skips rte_mempool_ops_get_info() for
such a mempool. The info is now zeroed first; it was left uninitialized
for drivers without a get_info operation.
The populating thread sets the flag without holding a lock, so set it
with an atomic fetch-or with release ordering, and test it with an
atomic load with acquire ordering. A reader that sees the flag then
also sees the store.
rte_mempool_walk() still passes mempools that are not populated yet to
its callback. Document that the callback must check
RTE_MEMPOOL_F_POOL_CREATED before querying the mempool driver.
Fixes: 449c49b93a6b ("mempool: support handler operations")
Fixes: ab8a1cc4e3d8 ("mempool: add object usage counts in telemetry")
Cc: [email protected]
Signed-off-by: Robert Sanford <[email protected]>
---
lib/mempool/rte_mempool.c | 36 ++++++++++++++++++++++++++++++++----
lib/mempool/rte_mempool.h | 9 +++++++++
2 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/lib/mempool/rte_mempool.c b/lib/mempool/rte_mempool.c
index d9f339a3c2..c9da42441a 100644
--- a/lib/mempool/rte_mempool.c
+++ b/lib/mempool/rte_mempool.c
@@ -294,6 +294,21 @@ rte_mempool_free_memchunks(struct rte_mempool *mp)
}
}
+/*
+ * Check whether the mempool driver has allocated its store. Mempools are on
+ * the mempool list before that, so rte_mempool_dump() and rte_mempool_walk()
+ * callbacks can find them while another thread populates them.
+ * Acquire: pairs with the release in mempool_ops_alloc_once().
+ */
+static bool
+mempool_is_created(const struct rte_mempool *mp)
+{
+ return (rte_atomic_load_explicit(
+ (const RTE_ATOMIC(unsigned int) *)&mp->flags,
+ rte_memory_order_acquire) &
+ RTE_MEMPOOL_F_POOL_CREATED) != 0;
+}
+
static int
mempool_ops_alloc_once(struct rte_mempool *mp)
{
@@ -304,7 +319,13 @@ mempool_ops_alloc_once(struct rte_mempool *mp)
ret = rte_mempool_ops_alloc(mp);
if (ret != 0)
return ret;
- mp->flags |= RTE_MEMPOOL_F_POOL_CREATED;
+ /*
+ * Release: make the store visible before the flag, see
+ * mempool_is_created().
+ */
+ rte_atomic_fetch_or_explicit(
+ (RTE_ATOMIC(unsigned int) *)&mp->flags,
+ RTE_MEMPOOL_F_POOL_CREATED, rte_memory_order_release);
}
return 0;
}
@@ -1283,10 +1304,14 @@ rte_mempool_dump(FILE *f, struct rte_mempool *mp)
unsigned common_count;
unsigned cache_count;
size_t mem_len = 0;
+ bool created;
RTE_ASSERT(f != NULL);
RTE_ASSERT(mp != NULL);
+ /* the mempool driver can only be queried once the store exists */
+ created = mempool_is_created(mp);
+
fprintf(f, "mempool <%s>@%p\n", mp->name, mp);
fprintf(f, " flags=%x\n", mp->flags);
fprintf(f, " socket_id=%d\n", mp->socket_id);
@@ -1318,14 +1343,16 @@ rte_mempool_dump(FILE *f, struct rte_mempool *mp)
}
cache_count = rte_mempool_dump_cache(f, mp);
- common_count = rte_mempool_ops_get_count(mp);
+ common_count = created ? rte_mempool_ops_get_count(mp) : 0;
if ((cache_count + common_count) > mp->size)
common_count = mp->size - cache_count;
fprintf(f, " common_pool_count=%u\n", common_count);
/* sum and dump statistics */
#ifdef RTE_LIBRTE_MEMPOOL_STATS
- rte_mempool_ops_get_info(mp, &info);
+ memset(&info, 0, sizeof(info));
+ if (created)
+ rte_mempool_ops_get_info(mp, &info);
memset(&sum, 0, sizeof(sum));
for (lcore_id = 0; lcore_id < RTE_MAX_LCORE + 1; lcore_id++) {
sum.put_bulk += mp->stats[lcore_id].put_bulk;
@@ -1653,7 +1680,8 @@ mempool_info_cb(struct rte_mempool *mp, void *arg)
cache_count += cache->len;
}
rte_tel_data_add_dict_uint(info->d, "total_cache_count", cache_count);
- common_count = rte_mempool_ops_get_count(mp);
+ /* the mempool driver can only be queried once the store exists */
+ common_count = mempool_is_created(mp) ? rte_mempool_ops_get_count(mp) :
0;
if ((cache_count + common_count) > mp->size)
common_count = mp->size - cache_count;
rte_tel_data_add_dict_uint(info->d, "common_pool_count", common_count);
diff --git a/lib/mempool/rte_mempool.h b/lib/mempool/rte_mempool.h
index bd58d8a9d5..d8413aa4bf 100644
--- a/lib/mempool/rte_mempool.h
+++ b/lib/mempool/rte_mempool.h
@@ -1325,6 +1325,9 @@ void rte_mempool_stats_reset(struct rte_mempool *mp);
/**
* Dump the status of the mempool to a file.
*
+ * For a mempool that is not populated yet, the mempool driver is not
+ * queried, and the common pool count is reported as 0.
+ *
* @param f
* A pointer to a file for output
* @param mp
@@ -1950,6 +1953,12 @@ uint32_t rte_mempool_calc_obj_size(uint32_t elt_size,
uint32_t flags,
* The mempool list is locked while the iterator function runs, so the
* iterator function must not create or free a mempool.
*
+ * The iterator function is also called for mempools that are not
+ * populated yet, which do not have RTE_MEMPOOL_F_POOL_CREATED set in
+ * their flags. Such a mempool has no store yet, so the iterator function
+ * must check this flag before calling functions that query the mempool
+ * driver, such as rte_mempool_avail_count().
+ *
* @param func
* Iterator function
* @param arg
--
2.34.1