patch 'hash: free replaced data on overwrite when RCU is configured' has been queued to stable release 25.11.1
Kevin Traynor
ktraynor at redhat.com
Thu Mar 19 11:03:08 CET 2026
Hi,
FYI, your patch has been queued to stable release 25.11.1
Note it hasn't been pushed to http://dpdk.org/browse/dpdk-stable yet.
It will be pushed if I get no objections before 03/23/26. So please
shout if anyone has objections.
Also note that after the patch there's a diff of the upstream commit vs the
patch applied to the branch. This will indicate if there was any rebasing
needed to apply to the stable branch. If there were code changes for rebasing
(ie: not only metadata diffs), please double check that the rebase was
correctly done.
Queued patches are on a temporary branch at:
https://github.com/kevintraynor/dpdk-stable
This queued commit can be viewed at:
https://github.com/kevintraynor/dpdk-stable/commit/beb195c9257f853addc749248bd1610dfcb6317b
Thanks.
Kevin
---
>From beb195c9257f853addc749248bd1610dfcb6317b Mon Sep 17 00:00:00 2001
From: Robin Jarry <rjarry at redhat.com>
Date: Fri, 6 Mar 2026 09:47:12 +0100
Subject: [PATCH] hash: free replaced data on overwrite when RCU is configured
[ upstream commit 50d943929acf3948df13ce0e5cdb126587ff9654 ]
When rte_hash_add_key_data() overwrites an existing key, the old data
pointer is silently lost. With RCU-protected readers still potentially
accessing the old data, the application has no safe way to free it.
When RCU is configured with a free_key_data_func callback, automatically
enqueue the old data for deferred freeing via the RCU defer queue on
overwrite. In SYNC mode, synchronize and call free_key_data_func
directly.
Fixes: 769b2de7fb52 ("hash: implement RCU resources reclamation")
Signed-off-by: Robin Jarry <rjarry at redhat.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev at huawei.com>
---
app/test/test_hash.c | 134 +++++++++++++++++++++++++++++++++++++
lib/hash/rte_cuckoo_hash.c | 38 ++++++++++-
lib/hash/rte_hash.h | 8 ++-
3 files changed, 177 insertions(+), 3 deletions(-)
diff --git a/app/test/test_hash.c b/app/test/test_hash.c
index 5791fd7f4c..8d79d69422 100644
--- a/app/test/test_hash.c
+++ b/app/test/test_hash.c
@@ -2322,4 +2322,135 @@ test_hash_rcu_qsbr_dq_reclaim(void)
}
+static void *old_data;
+
+static void
+test_hash_free_key_data_func(void *p __rte_unused, void *key_data)
+{
+ old_data = key_data;
+}
+
+/*
+ * Test automatic RCU free on overwrite via rte_hash_add_key_data.
+ * - Create hash with RW_CONCURRENCY_LF and RCU QSBR in DQ mode
+ * with a free_key_data_func callback that increments a counter.
+ * - Register a pseudo reader thread.
+ * - Add key with data (void *)1.
+ * - Overwrite same key with data (void *)2 via rte_hash_add_key_data.
+ * - Report quiescent state, trigger reclamation.
+ * - Verify the free callback was called exactly once.
+ * - Delete the key, report quiescent state, reclaim again.
+ * - Verify the free callback was called a second time.
+ */
+static int
+test_hash_rcu_qsbr_replace_auto_free(void)
+{
+ struct rte_hash_rcu_config rcu = {
+ .v = NULL,
+ .mode = RTE_HASH_QSBR_MODE_DQ,
+ .free_key_data_func = test_hash_free_key_data_func,
+ .key_data_ptr = NULL,
+ };
+ struct rte_hash_parameters params = {
+ .name = "test_replace_auto_free",
+ .entries = 16,
+ .key_len = sizeof(uint32_t),
+ .hash_func = NULL,
+ .hash_func_init_val = 0,
+ .socket_id = SOCKET_ID_ANY,
+ .extra_flag = RTE_HASH_EXTRA_FLAGS_RW_CONCURRENCY_LF,
+ };
+ struct rte_hash *hash = NULL;
+ uint32_t key = 55;
+ int32_t status;
+ int ret = -1;
+ size_t sz;
+
+ printf("\n# Running RCU replace auto-free test\n");
+
+ hash = rte_hash_create(¶ms);
+ if (hash == NULL) {
+ printf("hash creation failed\n");
+ goto end;
+ }
+
+ sz = rte_rcu_qsbr_get_memsize(RTE_MAX_LCORE);
+ rcu.v = rte_zmalloc(NULL, sz, RTE_CACHE_LINE_SIZE);
+ if (rcu.v == NULL) {
+ printf("RCU QSBR alloc failed\n");
+ goto end;
+ }
+ status = rte_rcu_qsbr_init(rcu.v, RTE_MAX_LCORE);
+ if (status != 0) {
+ printf("RCU QSBR init failed\n");
+ goto end;
+ }
+
+ status = rte_hash_rcu_qsbr_add(hash, &rcu);
+ if (status != 0) {
+ printf("RCU QSBR add failed\n");
+ goto end;
+ }
+
+ /* Register pseudo reader */
+ status = rte_rcu_qsbr_thread_register(rcu.v, 0);
+ if (status != 0) {
+ printf("RCU QSBR thread register failed\n");
+ goto end;
+ }
+ rte_rcu_qsbr_thread_online(rcu.v, 0);
+
+ old_data = NULL;
+
+ /* Add key with data = (void *)1 */
+ status = rte_hash_add_key_data(hash, &key, (void *)(uintptr_t)1);
+ if (status != 0) {
+ printf("failed to add key (status=%d)\n", status);
+ goto end;
+ }
+
+ /* Overwrite same key with data = (void *)2 */
+ status = rte_hash_add_key_data(hash, &key, (void *)(uintptr_t)2);
+ if (status != 0) {
+ printf("failed to overwrite key (status=%d)\n", status);
+ goto end;
+ }
+
+ /* Reader quiescent and reclaim */
+ rte_rcu_qsbr_quiescent(rcu.v, 0);
+ rte_hash_rcu_qsbr_dq_reclaim(hash, NULL, NULL, NULL);
+
+ if (old_data != (void *)(uintptr_t)1) {
+ printf("old data should be 0x1 but is %p\n", old_data);
+ goto end;
+ }
+
+ /* Delete the key */
+ status = rte_hash_del_key(hash, &key);
+ if (status < 0) {
+ printf("failed to delete key (status=%d)\n", status);
+ goto end;
+ }
+
+ /* Reader quiescent and reclaim again */
+ rte_rcu_qsbr_quiescent(rcu.v, 0);
+ rte_hash_rcu_qsbr_dq_reclaim(hash, NULL, NULL, NULL);
+
+ if (old_data != (void *)(uintptr_t)2) {
+ printf("old data should be 2 but is %p\n", old_data);
+ goto end;
+ }
+
+ ret = 0;
+end:
+ if (rcu.v != NULL) {
+ rte_rcu_qsbr_thread_offline(rcu.v, 0);
+ rte_rcu_qsbr_thread_unregister(rcu.v, 0);
+ }
+ rte_hash_free(hash);
+ rte_free(rcu.v);
+
+ return ret;
+}
+
/*
* Do all unit and performance tests.
@@ -2403,4 +2534,7 @@ test_hash(void)
return -1;
+ if (test_hash_rcu_qsbr_replace_auto_free() < 0)
+ return -1;
+
return 0;
}
diff --git a/lib/hash/rte_cuckoo_hash.c b/lib/hash/rte_cuckoo_hash.c
index d5663d11f7..36bdd6da00 100644
--- a/lib/hash/rte_cuckoo_hash.c
+++ b/lib/hash/rte_cuckoo_hash.c
@@ -76,4 +76,5 @@ struct __rte_hash_rcu_dq_entry {
uint32_t key_idx;
uint32_t ext_bkt_idx;
+ void *old_data;
};
@@ -747,4 +748,26 @@ enqueue_slot_back(const struct rte_hash *h,
}
+/*
+ * When RCU is configured with a free function, auto-free the overwritten
+ * data pointer via RCU.
+ */
+static inline void
+__rte_hash_rcu_auto_free_old_data(const struct rte_hash *h, void *d)
+{
+ struct __rte_hash_rcu_dq_entry rcu_dq_entry = {
+ .key_idx = EMPTY_SLOT, /* sentinel value for __hash_rcu_qsbr_free_resource */
+ .old_data = d,
+ };
+
+ if (d == NULL || h->hash_rcu_cfg == NULL || h->hash_rcu_cfg->free_key_data_func == NULL)
+ return;
+
+ if (h->dq == NULL || rte_rcu_qsbr_dq_enqueue(h->dq, &rcu_dq_entry) != 0) {
+ /* SYNC mode or enqueue failed in DQ mode */
+ rte_rcu_qsbr_synchronize(h->hash_rcu_cfg->v, RTE_QSBR_THRID_INVALID);
+ h->hash_rcu_cfg->free_key_data_func(h->hash_rcu_cfg->key_data_ptr, d);
+ }
+}
+
/* Search a key from bucket and update its data.
* Writer holds the lock before calling this.
@@ -756,4 +779,5 @@ search_and_update(const struct rte_hash *h, void *data, const void *key,
int i;
struct rte_hash_key *k, *keys = h->key_store;
+ void *old_data;
for (i = 0; i < RTE_HASH_BUCKET_ENTRIES; i++) {
@@ -768,7 +792,10 @@ search_and_update(const struct rte_hash *h, void *data, const void *key,
* to the readers.
*/
- rte_atomic_store_explicit(&k->pdata,
+ old_data = rte_atomic_exchange_explicit(&k->pdata,
data,
rte_memory_order_release);
+
+ __rte_hash_rcu_auto_free_old_data(h, old_data);
+
/*
* Return index where key is stored,
@@ -1552,4 +1579,13 @@ __hash_rcu_qsbr_free_resource(void *p, void *e, unsigned int n)
RTE_SET_USED(n);
+
+ if (rcu_dq_entry.key_idx == EMPTY_SLOT) {
+ /* Overwrite case: free old data only, do not recycle slot */
+ RTE_ASSERT(h->hash_rcu_cfg->free_key_data_func != NULL);
+ h->hash_rcu_cfg->free_key_data_func(h->hash_rcu_cfg->key_data_ptr,
+ rcu_dq_entry.old_data);
+ return;
+ }
+
keys = h->key_store;
diff --git a/lib/hash/rte_hash.h b/lib/hash/rte_hash.h
index f692e0868d..e33f0aea0f 100644
--- a/lib/hash/rte_hash.h
+++ b/lib/hash/rte_hash.h
@@ -227,5 +227,7 @@ rte_hash_max_key_id(const struct rte_hash *h);
* table creation.
* If the key exists already in the table, this API updates its value
- * with 'data' passed in this API. It is the responsibility of
+ * with 'data' passed in this API. If RCU is configured with a
+ * free_key_data_func callback, the old data is automatically
+ * deferred-freed via RCU. Otherwise, it is the responsibility of
* the application to manage any memory associated with the old value.
* The readers might still be using the old value even after this API
@@ -254,5 +256,7 @@ rte_hash_add_key_data(const struct rte_hash *h, const void *key, void *data);
* table creation.
* If the key exists already in the table, this API updates its value
- * with 'data' passed in this API. It is the responsibility of
+ * with 'data' passed in this API. If RCU is configured with a
+ * free_key_data_func callback, the old data is automatically
+ * deferred-freed via RCU. Otherwise, it is the responsibility of
* the application to manage any memory associated with the old value.
* The readers might still be using the old value even after this API
--
2.53.0
---
Diff of the applied patch vs upstream commit (please double-check if non-empty:
---
--- - 2026-03-19 10:01:09.107197156 +0000
+++ 0070-hash-free-replaced-data-on-overwrite-when-RCU-is-con.patch 2026-03-19 10:01:07.127331260 +0000
@@ -1 +1 @@
-From 50d943929acf3948df13ce0e5cdb126587ff9654 Mon Sep 17 00:00:00 2001
+From beb195c9257f853addc749248bd1610dfcb6317b Mon Sep 17 00:00:00 2001
@@ -5,0 +6,2 @@
+[ upstream commit 50d943929acf3948df13ce0e5cdb126587ff9654 ]
+
@@ -16 +17,0 @@
-Cc: stable at dpdk.org
@@ -21,5 +22,4 @@
- app/test/test_hash.c | 134 +++++++++++++++++++++++++
- doc/guides/rel_notes/release_26_03.rst | 7 ++
- lib/hash/rte_cuckoo_hash.c | 38 ++++++-
- lib/hash/rte_hash.h | 8 +-
- 4 files changed, 184 insertions(+), 3 deletions(-)
+ app/test/test_hash.c | 134 +++++++++++++++++++++++++++++++++++++
+ lib/hash/rte_cuckoo_hash.c | 38 ++++++++++-
+ lib/hash/rte_hash.h | 8 ++-
+ 3 files changed, 177 insertions(+), 3 deletions(-)
@@ -28 +28 @@
-index 3fb3d96d05..56a7779e09 100644
+index 5791fd7f4c..8d79d69422 100644
@@ -31 +31 @@
-@@ -2343,4 +2343,135 @@ test_hash_rcu_qsbr_dq_reclaim(void)
+@@ -2322,4 +2322,135 @@ test_hash_rcu_qsbr_dq_reclaim(void)
@@ -167 +167 @@
-@@ -2424,4 +2555,7 @@ test_hash(void)
+@@ -2403,4 +2534,7 @@ test_hash(void)
@@ -175,21 +174,0 @@
-diff --git a/doc/guides/rel_notes/release_26_03.rst b/doc/guides/rel_notes/release_26_03.rst
-index 77b8cc1187..3d2ed19eb8 100644
---- a/doc/guides/rel_notes/release_26_03.rst
-+++ b/doc/guides/rel_notes/release_26_03.rst
-@@ -126,4 +126,10 @@ New Features
- and their HMAC variants.
-
-+* **Added automatic deferred free on hash data overwrite.**
-+
-+ When RCU is configured with a ``free_key_data_func`` callback,
-+ ``rte_hash_add_key_data`` now automatically defers
-+ freeing the old data pointer on key overwrite via the RCU defer queue.
-+
- * **Added Ctrl+L support to cmdline library.**
-
-@@ -131,4 +137,5 @@ New Features
- to clear the screen before redisplaying the prompt.
-
-+
- Removed Items
- -------------
@@ -197 +176 @@
-index 36b3b477a5..69dcfdfc39 100644
+index d5663d11f7..36bdd6da00 100644
@@ -206 +185 @@
-@@ -762,4 +763,26 @@ enqueue_slot_back(const struct rte_hash *h,
+@@ -747,4 +748,26 @@ enqueue_slot_back(const struct rte_hash *h,
@@ -233 +212 @@
-@@ -771,4 +794,5 @@ search_and_update(const struct rte_hash *h, void *data, const void *key,
+@@ -756,4 +779,5 @@ search_and_update(const struct rte_hash *h, void *data, const void *key,
@@ -239 +218 @@
-@@ -783,7 +807,10 @@ search_and_update(const struct rte_hash *h, void *data, const void *key,
+@@ -768,7 +792,10 @@ search_and_update(const struct rte_hash *h, void *data, const void *key,
@@ -251 +230 @@
-@@ -1567,4 +1594,13 @@ __hash_rcu_qsbr_free_resource(void *p, void *e, unsigned int n)
+@@ -1552,4 +1579,13 @@ __hash_rcu_qsbr_free_resource(void *p, void *e, unsigned int n)
More information about the stable
mailing list