// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2023. Huawei Technologies Co., Ltd */ #define _GNU_SOURCE #include #include #include #include "htab_reuse.skel.h" struct htab_op_ctx { int fd; int loop; bool stop; }; struct htab_val { unsigned int lock; unsigned int data; }; static void *htab_lookup_fn(void *arg) { struct htab_op_ctx *ctx = arg; int i = 0; while (i++ < ctx->loop && !ctx->stop) { struct htab_val value; unsigned int key; /* Use BPF_F_LOCK to use spin-lock in map value. */ key = 7; bpf_map_lookup_elem_flags(ctx->fd, &key, &value, BPF_F_LOCK); } return NULL; } static void *htab_update_fn(void *arg) { struct htab_op_ctx *ctx = arg; int i = 0; while (i++ < ctx->loop && !ctx->stop) { struct htab_val value; unsigned int key; key = 7; value.lock = 0; value.data = key; bpf_map_update_elem(ctx->fd, &key, &value, BPF_F_LOCK); bpf_map_delete_elem(ctx->fd, &key); key = 24; value.lock = 0; value.data = key; bpf_map_update_elem(ctx->fd, &key, &value, BPF_F_LOCK); bpf_map_delete_elem(ctx->fd, &key); } return NULL; } static void test_htab_reuse_basic(void) { unsigned int i, wr_nr = 1, rd_nr = 4; pthread_t tids[wr_nr + rd_nr]; struct htab_reuse *skel; struct htab_op_ctx ctx; int err; skel = htab_reuse__open_and_load(); if (!ASSERT_OK_PTR(skel, "htab_reuse__open_and_load")) return; ctx.fd = bpf_map__fd(skel->maps.htab); ctx.loop = 500; ctx.stop = false; memset(tids, 0, sizeof(tids)); for (i = 0; i < wr_nr; i++) { err = pthread_create(&tids[i], NULL, htab_update_fn, &ctx); if (!ASSERT_OK(err, "pthread_create")) { ctx.stop = true; goto reap; } } for (i = 0; i < rd_nr; i++) { err = pthread_create(&tids[i + wr_nr], NULL, htab_lookup_fn, &ctx); if (!ASSERT_OK(err, "pthread_create")) { ctx.stop = true; goto reap; } } reap: for (i = 0; i < wr_nr + rd_nr; i++) { if (!tids[i]) continue; pthread_join(tids[i], NULL); } htab_reuse__destroy(skel); } /* * Writes consistency test for BPF_F_LOCK update * * The race: * 1. Thread A: BPF_F_LOCK|BPF_EXIST update * 2. Thread B: delete element then update it with BPF_ANY */ struct htab_val_large { struct bpf_spin_lock lock; __u32 seq; __u64 data[256]; }; struct consistency_ctx { int fd; int start_fd; int loop; volatile bool torn_write; }; static void wait_for_start(int fd) { char buf; read(fd, &buf, 1); } static void *locked_update_fn(void *arg) { struct consistency_ctx *ctx = arg; struct htab_val_large value; unsigned int key = 1; int i; memset(&value, 0xAA, sizeof(value)); wait_for_start(ctx->start_fd); for (i = 0; i < ctx->loop; i++) { value.seq = i; bpf_map_update_elem(ctx->fd, &key, &value, BPF_F_LOCK | BPF_EXIST); } return NULL; } /* Delete + update: removes the element then re-creates it with BPF_ANY. */ static void *delete_update_fn(void *arg) { struct consistency_ctx *ctx = arg; struct htab_val_large value; unsigned int key = 1; int i; memset(&value, 0xBB, sizeof(value)); wait_for_start(ctx->start_fd); for (i = 0; i < ctx->loop; i++) { value.seq = i; bpf_map_delete_elem(ctx->fd, &key); bpf_map_update_elem(ctx->fd, &key, &value, BPF_ANY | BPF_F_LOCK); } return NULL; } static void *locked_lookup_fn(void *arg) { struct consistency_ctx *ctx = arg; struct htab_val_large value; unsigned int key = 1; int i, j; wait_for_start(ctx->start_fd); for (i = 0; i < ctx->loop && !ctx->torn_write; i++) { if (bpf_map_lookup_elem_flags(ctx->fd, &key, &value, BPF_F_LOCK)) continue; for (j = 0; j < 256; j++) { if (value.data[j] != value.data[0]) { ctx->torn_write = true; return NULL; } } } return NULL; } static void test_htab_reuse_consistency(void) { int threads_total = 6, threads = 2; pthread_t tids[threads_total]; struct consistency_ctx ctx; struct htab_val_large seed; struct htab_reuse *skel; unsigned int key = 1, i; int pipefd[2]; int err; skel = htab_reuse__open_and_load(); if (!ASSERT_OK_PTR(skel, "htab_reuse__open_and_load")) return; if (!ASSERT_OK(pipe(pipefd), "pipe")) goto out; ctx.fd = bpf_map__fd(skel->maps.htab_lock_consistency); ctx.start_fd = pipefd[0]; ctx.loop = 100000; ctx.torn_write = false; /* Seed the element so locked updaters have something to find */ memset(&seed, 0xBB, sizeof(seed)); err = bpf_map_update_elem(ctx.fd, &key, &seed, BPF_ANY); if (!ASSERT_OK(err, "seed_element")) goto close_pipe; memset(tids, 0, sizeof(tids)); for (i = 0; i < threads; i++) { err = pthread_create(&tids[i], NULL, locked_update_fn, &ctx); if (!ASSERT_OK(err, "pthread_create")) goto stop; } for (i = 0; i < threads; i++) { err = pthread_create(&tids[threads + i], NULL, delete_update_fn, &ctx); if (!ASSERT_OK(err, "pthread_create")) goto stop; } for (i = 0; i < threads; i++) { err = pthread_create(&tids[threads * 2 + i], NULL, locked_lookup_fn, &ctx); if (!ASSERT_OK(err, "pthread_create")) goto stop; } /* Release all threads simultaneously */ close(pipefd[1]); pipefd[1] = -1; stop: for (i = 0; i < threads_total; i++) { if (!tids[i]) continue; pthread_join(tids[i], NULL); } ASSERT_FALSE(ctx.torn_write, "no torn writes detected"); close_pipe: if (pipefd[1] >= 0) close(pipefd[1]); close(pipefd[0]); out: htab_reuse__destroy(skel); } void test_htab_reuse(void) { if (test__start_subtest("basic")) test_htab_reuse_basic(); if (test__start_subtest("consistency")) test_htab_reuse_consistency(); }