* [PATCH bpf 0/2] bpf: Fix RHASH batch cursor loss @ 2026-11-07 13:11 Tianyi Chen 2026-11-07 13:11 ` [PATCH bpf 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen 2026-11-07 13:11 ` [PATCH bpf 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen 0 siblings, 2 replies; 5+ messages in thread From: Tianyi Chen @ 2026-11-07 13:11 UTC (permalink / raw) To: bpf Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, yatsenko, shuah, linux-kselftest RHASH batch lookups can report end-of-map when a key is deleted during the walk. Return EAGAIN for that case and add a regression test. Tested in an x86-64 VM: the regression fails without the fix and all five subtests pass with it. The bpftool batch and recursive tests also pass, separately and together. Tianyi Chen (2): bpf: Report lost RHASH batch traversal keys as EAGAIN selftests/bpf: Cover RHASH batch cursor loss kernel/bpf/hashtab.c | 13 +- .../selftests/bpf/prog_tests/rhash_batch.c | 181 ++++++++++++++++++ 2 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/rhash_batch.c base-commit: 646114a0d2f39e52f2204196ac12a6fe5a5a3fd3 -- 2.56.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN 2026-11-07 13:11 [PATCH bpf 0/2] bpf: Fix RHASH batch cursor loss Tianyi Chen @ 2026-11-07 13:11 ` Tianyi Chen 2026-11-09 18:53 ` Mykyta Yatsenko 2026-11-07 13:11 ` [PATCH bpf 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen 1 sibling, 1 reply; 5+ messages in thread From: Tianyi Chen @ 2026-11-07 13:11 UTC (permalink / raw) To: bpf Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, yatsenko, shuah, linux-kselftest 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 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] 5+ messages in thread
* Re: [PATCH bpf 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN 2026-11-07 13:11 ` [PATCH bpf 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen @ 2026-11-09 18:53 ` Mykyta Yatsenko 0 siblings, 0 replies; 5+ messages in thread From: Mykyta Yatsenko @ 2026-11-09 18:53 UTC (permalink / raw) To: Tianyi Chen, bpf Cc: ast, daniel, andrii, eddyz87, memxor, yatsenko, shuah, linux-kselftest On 11/7/26 1:11 PM, Tianyi Chen wrote: > 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 > Assisted-by: LLM > Signed-off-by: Tianyi Chen <hi@tychen.cc> > --- Looks correct: transform -ENOENT returned from rhashtable_next_key() to -EAGAIN indicating loss of cursor. rhashtable_next_key() returns NULL when iteration is exhausted, that is transformed to -ENOENT in if (!has_next_cursor) Acked-by: Mykyta Yatsenko <yatsenko@meta.com> > 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) || ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf 2/2] selftests/bpf: Cover RHASH batch cursor loss 2026-11-07 13:11 [PATCH bpf 0/2] bpf: Fix RHASH batch cursor loss Tianyi Chen 2026-11-07 13:11 ` [PATCH bpf 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen @ 2026-11-07 13:11 ` Tianyi Chen 2026-11-07 13:22 ` sashiko-bot 1 sibling, 1 reply; 5+ messages in thread From: Tianyi Chen @ 2026-11-07 13:11 UTC (permalink / raw) To: bpf Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, yatsenko, shuah, linux-kselftest 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> --- .../selftests/bpf/prog_tests/rhash_batch.c | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/rhash_batch.c diff --git a/tools/testing/selftests/bpf/prog_tests/rhash_batch.c b/tools/testing/selftests/bpf/prog_tests/rhash_batch.c new file mode 100644 index 000000000000..bba9062afd12 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/rhash_batch.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <test_progs.h> +#include <pthread.h> + +#define ENTRIES 64 +#define VALUE_SIZE 4000 + +static int create_map(__u32 entries) +{ + LIBBPF_OPTS(bpf_map_create_opts, opts, + .map_flags = BPF_F_NO_PREALLOC, + .map_extra = entries); + char value[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 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 test_batch(bool delete, bool lost_cursor) +{ + __u64 key, cursor, seen = 0; + char value[VALUE_SIZE]; + __u32 count; + int fd, err; + void *in = NULL; + + /* Two entries fit in the minimum table, including while deleting. */ + fd = create_map(2); + if (fd < 0) + return; + if (lost_cursor) { + count = 1; + err = 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 = 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 = 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 = 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 race_ctx { + int fd; + __u64 key; + bool stop; + int err; + unsigned int updates; +}; + +static void *update_key(void *arg) +{ + struct race_ctx *ctx = arg; + char value[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 test_concurrent_delete(void) +{ + struct race_ctx ctx = {}; + __u64 keys[ENTRIES], cursor, seen = 0, expected; + unsigned int completed = 0, attempt; + __u32 count, i; + void *in = NULL, *values; + pthread_t thread; + int err; + + ctx.fd = create_map(ENTRIES); + if (ctx.fd < 0) + return; + values = malloc(ENTRIES * 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, 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 = 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], 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_batch(void) +{ + if (test__start_subtest("lookup")) + test_batch(false, false); + if (test__start_subtest("lookup_delete")) + test_batch(true, false); + if (test__start_subtest("lost_lookup_cursor")) + test_batch(false, true); + if (test__start_subtest("lost_delete_cursor")) + test_batch(true, true); + if (test__start_subtest("concurrent_delete")) + test_concurrent_delete(); +} -- 2.56.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf 2/2] selftests/bpf: Cover RHASH batch cursor loss 2026-11-07 13:11 ` [PATCH bpf 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen @ 2026-11-07 13:22 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-11-07 13:22 UTC (permalink / raw) To: Tianyi Chen, bpf Thank you for your contribution! Sashiko AI review found 1 potential issue to consider: - [Low] selftests/bpf: standalone rhash_batch.c file instead of extending rhash.c -- commit bf9586f48a54e730e2740cce0745ca08b2ddac63 Author: Tianyi Chen <hi@tychen.cc> selftests/bpf: Cover RHASH batch cursor loss This commit adds BPF selftests covering normal completion, missing input cursors, and concurrent deletions for BPF_MAP_TYPE_RHASH batch lookup and lookup-and-delete operations. > diff --git a/tools/testing/selftests/bpf/prog_tests/rhash_batch.c b/tools/testing/selftests/bpf/prog_tests/rhash_batch.c > new file mode 100644 > index 0000000000000..bba9062afd123 > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/rhash_batch.c [ ... ] > +void test_rhash_batch(void) > +{ > + if (test__start_subtest("lookup")) > + test_batch(false, false); > + if (test__start_subtest("lookup_delete")) > + test_batch(true, false); > + if (test__start_subtest("lost_lookup_cursor")) > + test_batch(false, true); > + if (test__start_subtest("lost_delete_cursor")) > + test_batch(true, true); > + if (test__start_subtest("concurrent_delete")) > + test_concurrent_delete(); > +} [Severity: Low] This isn't a bug, but could these subtests be added to test_rhash() in tools/testing/selftests/bpf/prog_tests/rhash.c instead of introducing a separate file? rhash.c already houses userspace map syscall and iteration subtests for BPF_MAP_TYPE_RHASH, and rhash_batch.c does not load a separate BPF program skeleton. -- Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794056839.git.hi@tychen.cc?part=2 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-09 18:53 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-07 13:11 [PATCH bpf 0/2] bpf: Fix RHASH batch cursor loss Tianyi Chen 2026-11-07 13:11 ` [PATCH bpf 1/2] bpf: Report lost RHASH batch traversal keys as EAGAIN Tianyi Chen 2026-11-09 18:53 ` Mykyta Yatsenko 2026-11-07 13:11 ` [PATCH bpf 2/2] selftests/bpf: Cover RHASH batch cursor loss Tianyi Chen 2026-11-07 13:22 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox