* [PATCH bpf v2 0/2] bpf: Fix RHASH batch cursor loss
@ 2026-11-11 7:18 Tianyi Chen
2026-11-11 7:18 ` [PATCH bpf v2 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen
2026-11-11 7:18 ` [PATCH bpf v2 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen
0 siblings, 2 replies; 3+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:18 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, yatsenko
Report a lost RHASH traversal key as -EAGAIN rather than end-of-iteration.
The kernel change is unchanged from v1.
Changes in v2:
- Add the batch subtests to the existing rhash.c suite.
- Carry Mykyta's Acked-by on the fix.
All 17 RHASH subtests pass in x86-64 KVM, with five further successful
runs of the five batch subtests. The concurrent-delete test still fails
on the unpatched kernel.
v1: https://lore.kernel.org/r/cover.1794056839.git.hi@tychen.cc
Tianyi Chen (2):
bpf: Report lost RHASH batch traversal keys as EAGAIN
selftests/bpf: Cover RHASH batch cursor loss
kernel/bpf/hashtab.c | 13 +-
.../testing/selftests/bpf/prog_tests/rhash.c | 177 ++++++++++++++++++
2 files changed, 184 insertions(+), 6 deletions(-)
base-commit: 475a0cfd02f14d764023e926ac01fc7597134229
--
2.56.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH bpf v2 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN
2026-11-11 7:18 [PATCH bpf v2 0/2] bpf: Fix RHASH batch cursor loss Tianyi Chen
@ 2026-11-11 7:18 ` Tianyi Chen
2026-11-11 7:18 ` [PATCH bpf v2 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen
1 sibling, 0 replies; 3+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:18 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, yatsenko
If a key is deleted after a batch copies it, rhashtable_next_key()
returns ERR_PTR(-ENOENT). The batch code treats the missing next cursor
as end-of-iteration and returns -ENOENT with a partial batch, so callers
can silently stop before reading keys that are still present.
Translate a lost traversal key to -EAGAIN, as for a missing input cursor,
and preserve other iterator errors. Still copy the collected entries and
update the count, including for lookup-and-delete batches.
Fixes: 818e00848227 ("bpf: Implement iteration ops for resizable hashtab")
Link: https://github.com/kernel-patches/bpf/pull/14700#issuecomment-6641291019
Acked-by: Mykyta Yatsenko <yatsenko@meta.com>
Assisted-by: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
kernel/bpf/hashtab.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/kernel/bpf/hashtab.c b/kernel/bpf/hashtab.c
index 6e2115477cb9..346a2afb4ad3 100644
--- a/kernel/bpf/hashtab.c
+++ b/kernel/bpf/hashtab.c
@@ -3556,14 +3556,15 @@ static int __rhtab_map_lookup_and_delete_batch(struct bpf_map *map,
rcu_read_unlock();
- if (total == 0) {
+ if (IS_ERR(elem)) {
+ ret = PTR_ERR(elem);
+ if (ret == -ENOENT)
+ ret = -EAGAIN;
+ } else if (!has_next_cursor) {
ret = -ENOENT;
- goto free;
}
-
- /* No more elements after this batch. */
- if (!has_next_cursor)
- ret = -ENOENT;
+ if (!total)
+ goto free;
if (copy_to_user(ukeys, keys, (size_t)total * key_size) ||
copy_to_user(uvalues, values, (size_t)total * value_size) ||
--
2.56.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH bpf v2 2/2] selftests/bpf: Cover RHASH batch cursor loss
2026-11-11 7:18 [PATCH bpf v2 0/2] bpf: Fix RHASH batch cursor loss Tianyi Chen
2026-11-11 7:18 ` [PATCH bpf v2 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen
@ 2026-11-11 7:18 ` Tianyi Chen
1 sibling, 0 replies; 3+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:18 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, yatsenko
Check normal completion and missing input cursors for both batch lookup
operations. Exercise a concurrent delete within a lookup batch by
repeatedly removing and reinserting the first key while the other keys
remain present. Every walk that reports end-of-iteration must include
those unchanged keys; a lost cursor may instead return EAGAIN.
Pre-size the map and keep its population stable to avoid testing the
separate best-effort behavior during resizing.
Assisted-by: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
.../testing/selftests/bpf/prog_tests/rhash.c | 177 ++++++++++++++++++
1 file changed, 177 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/rhash.c b/tools/testing/selftests/bpf/prog_tests/rhash.c
index 0641bd5b0a9e..12b18636b577 100644
--- a/tools/testing/selftests/bpf/prog_tests/rhash.c
+++ b/tools/testing/selftests/bpf/prog_tests/rhash.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
+#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include "rhash.skel.h"
@@ -149,6 +150,171 @@ static void rhash_iter_test(void)
bpf_iter_bpf_rhash_map__destroy(skel);
}
+#define BATCH_ENTRIES 64
+#define BATCH_VALUE_SIZE 4000
+
+static int rhash_batch_map_create(__u32 entries)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts,
+ .map_flags = BPF_F_NO_PREALLOC,
+ .map_extra = entries);
+ char value[BATCH_VALUE_SIZE] = {};
+ __u64 key;
+ int fd;
+
+ fd = bpf_map_create(BPF_MAP_TYPE_RHASH, "rhash_batch", sizeof(key),
+ sizeof(value), entries, &opts);
+ if (!ASSERT_OK_FD(fd, "map_create"))
+ return -1;
+ for (key = 0; key < entries; key++) {
+ if (!ASSERT_OK(bpf_map_update_elem(fd, &key, value, BPF_ANY), "map_update")) {
+ close(fd);
+ return -1;
+ }
+ }
+ return fd;
+}
+
+static int rhash_lookup_batch(int fd, void *in, void *out, void *keys,
+ void *values, __u32 *count, bool delete)
+{
+ if (delete)
+ return bpf_map_lookup_and_delete_batch(fd, in, out, keys, values, count, NULL);
+ return bpf_map_lookup_batch(fd, in, out, keys, values, count, NULL);
+}
+
+static void rhash_batch_test(bool delete, bool lost_cursor)
+{
+ __u64 key, cursor, seen = 0;
+ char value[BATCH_VALUE_SIZE];
+ __u32 count;
+ int fd, err;
+ void *in = NULL;
+
+ /* Two entries fit in the minimum table, including while deleting. */
+ fd = rhash_batch_map_create(2);
+ if (fd < 0)
+ return;
+ if (lost_cursor) {
+ count = 1;
+ err = rhash_lookup_batch(fd, NULL, &cursor, &key, value, &count, delete);
+ if (!ASSERT_OK(err, "first_batch") || !ASSERT_EQ(count, 1, "first_count") ||
+ !ASSERT_OK(bpf_map_delete_elem(fd, &cursor), "delete_cursor"))
+ goto out;
+ count = 1;
+ err = rhash_lookup_batch(fd, &cursor, &cursor, &key, value, &count, delete);
+ ASSERT_EQ(err, -EAGAIN, "lost_cursor");
+ ASSERT_EQ(count, 0, "lost_count");
+ goto out;
+ }
+ do {
+ count = 1;
+ err = rhash_lookup_batch(fd, in, &cursor, &key, value, &count, delete);
+ if (!ASSERT_TRUE(!err || err == -ENOENT, "batch_result") ||
+ !ASSERT_EQ(count, 1, "batch_count") ||
+ !ASSERT_LT(key, 2, "key_range") ||
+ !ASSERT_EQ(seen & (1ULL << key), 0, "unique_key"))
+ goto out;
+ seen |= 1ULL << key;
+ in = &cursor;
+ } while (!err);
+ ASSERT_EQ(seen, 3, "all_keys");
+ if (delete) {
+ ASSERT_EQ(bpf_map_get_next_key(fd, NULL, &cursor), -ENOENT, "empty_map");
+ count = 1;
+ err = rhash_lookup_batch(fd, NULL, &cursor, &key, value, &count, true);
+ ASSERT_EQ(err, -ENOENT, "empty_batch");
+ ASSERT_EQ(count, 0, "empty_count");
+ }
+out:
+ close(fd);
+}
+
+struct batch_race_ctx {
+ int fd;
+ __u64 key;
+ bool stop;
+ int err;
+ unsigned int updates;
+};
+
+static void *rhash_batch_update_key(void *arg)
+{
+ struct batch_race_ctx *ctx = arg;
+ char value[BATCH_VALUE_SIZE] = {};
+
+ while (!__atomic_load_n(&ctx->stop, __ATOMIC_RELAXED)) {
+ ctx->err = bpf_map_delete_elem(ctx->fd, &ctx->key);
+ if (ctx->err)
+ break;
+ ctx->err = bpf_map_update_elem(ctx->fd, &ctx->key, value, BPF_ANY);
+ if (ctx->err)
+ break;
+ ctx->updates++;
+ }
+ return NULL;
+}
+
+static void rhash_batch_concurrent_delete(void)
+{
+ struct batch_race_ctx ctx = {};
+ __u64 keys[BATCH_ENTRIES], cursor, seen = 0, expected;
+ unsigned int completed = 0, attempt;
+ __u32 count, i;
+ void *in = NULL, *values;
+ pthread_t thread;
+ int err;
+
+ ctx.fd = rhash_batch_map_create(BATCH_ENTRIES);
+ if (ctx.fd < 0)
+ return;
+ values = malloc(BATCH_ENTRIES * BATCH_VALUE_SIZE);
+ if (!ASSERT_OK_PTR(values, "values"))
+ goto close;
+ if (!ASSERT_OK(bpf_map_get_next_key(ctx.fd, NULL, &ctx.key), "first_key"))
+ goto out;
+ expected = ~(1ULL << ctx.key);
+ if (!ASSERT_OK(pthread_create(&thread, NULL, rhash_batch_update_key, &ctx),
+ "thread_create"))
+ goto out;
+ /* Only the first key changes; no resize is needed at this load. */
+ for (attempt = 0; attempt < 5000; attempt++) {
+ count = BATCH_ENTRIES;
+ err = bpf_map_lookup_batch(ctx.fd, in, &cursor, keys, values, &count, NULL);
+ if (err == -EAGAIN) {
+ in = NULL;
+ seen = 0;
+ continue;
+ }
+ if (!ASSERT_TRUE(!err || err == -ENOENT, "batch_result"))
+ break;
+ for (i = 0; i < count; i++) {
+ if (!ASSERT_LT(keys[i], BATCH_ENTRIES, "key_range"))
+ goto stop;
+ seen |= 1ULL << keys[i];
+ }
+ if (err == -ENOENT) {
+ if (!ASSERT_EQ(seen & expected, expected, "persistent_keys"))
+ break;
+ completed++;
+ in = NULL;
+ seen = 0;
+ } else {
+ in = &cursor;
+ }
+ }
+stop:
+ __atomic_store_n(&ctx.stop, true, __ATOMIC_RELAXED);
+ pthread_join(thread, NULL);
+ ASSERT_OK(ctx.err, "writer_result");
+ ASSERT_GT(ctx.updates, 0, "concurrent_updates");
+ ASSERT_GT(completed, 0, "completed_walks");
+out:
+ free(values);
+close:
+ close(ctx.fd);
+}
+
void test_rhash(void)
{
if (test__start_subtest("test_rhash_lookup_update"))
@@ -186,4 +352,15 @@ void test_rhash(void)
if (test__start_subtest("test_rhash_iter"))
rhash_iter_test();
+
+ if (test__start_subtest("test_rhash_batch_lookup"))
+ rhash_batch_test(false, false);
+ if (test__start_subtest("test_rhash_batch_lookup_delete"))
+ rhash_batch_test(true, false);
+ if (test__start_subtest("test_rhash_batch_lost_lookup_cursor"))
+ rhash_batch_test(false, true);
+ if (test__start_subtest("test_rhash_batch_lost_delete_cursor"))
+ rhash_batch_test(true, true);
+ if (test__start_subtest("test_rhash_batch_concurrent_delete"))
+ rhash_batch_concurrent_delete();
}
--
2.56.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-11-11 7:18 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 7:18 [PATCH bpf v2 0/2] bpf: Fix RHASH batch cursor loss Tianyi Chen
2026-11-11 7:18 ` [PATCH bpf v2 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen
2026-11-11 7:18 ` [PATCH bpf v2 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox