No unit test exist currently for the ovs-barrier type. It is however crucial as a building block and should be verified to work as expected.
Create a simple test verifying the basic function of ovs-barrier. Integrate the test as part of the test suite. Signed-off-by: Gaetan Rivet <gr...@u256.net> --- tests/automake.mk | 1 + tests/library.at | 5 + tests/test-barrier.c | 264 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 tests/test-barrier.c diff --git a/tests/automake.mk b/tests/automake.mk index 1a528aa39..a32abd41c 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -448,6 +448,7 @@ tests_ovstest_SOURCES = \ tests/ovstest.h \ tests/test-aes128.c \ tests/test-atomic.c \ + tests/test-barrier.c \ tests/test-bundle.c \ tests/test-byte-order.c \ tests/test-classifier.c \ diff --git a/tests/library.at b/tests/library.at index 1702b7556..e572c22e3 100644 --- a/tests/library.at +++ b/tests/library.at @@ -246,6 +246,11 @@ AT_SETUP([ofpbuf module]) AT_CHECK([ovstest test-ofpbuf], [0], []) AT_CLEANUP +AT_SETUP([barrier module]) +AT_KEYWORDS([barrier]) +AT_CHECK([ovstest test-barrier], [0], []) +AT_CLEANUP + AT_SETUP([rcu]) AT_CHECK([ovstest test-rcu-quiesce], [0], []) AT_CLEANUP diff --git a/tests/test-barrier.c b/tests/test-barrier.c new file mode 100644 index 000000000..3bc5291cc --- /dev/null +++ b/tests/test-barrier.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2021 NVIDIA Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <getopt.h> + +#include <config.h> + +#include "ovs-thread.h" +#include "ovs-rcu.h" +#include "ovstest.h" +#include "random.h" +#include "util.h" + +#define DEFAULT_N_THREADS 4 +#define NB_STEPS 4 + +static bool verbose; +static struct ovs_barrier barrier; + +struct blocker_aux { + unsigned int tid; + bool leader; + int step; +}; + +static void * +basic_blocker_main(void *aux_) +{ + struct blocker_aux *aux = aux_; + size_t i; + + aux->step = 0; + for (i = 0; i < NB_STEPS; i++) { + ovs_barrier_block(&barrier); + aux->step++; + ovs_barrier_block(&barrier); + } + + return NULL; +} + +static void +basic_block_check(struct blocker_aux *aux, size_t n, int expected) +{ + size_t i; + + for (i = 0; i < n; i++) { + if (verbose) { + printf("aux[%" PRIuSIZE "]=%d == %d", i, aux[i].step, expected); + if (aux[i].step != expected) { + printf(" <--- X"); + } + printf("\n"); + } else { + ovs_assert(aux[i].step == expected); + } + } + ovs_barrier_block(&barrier); + ovs_barrier_block(&barrier); +} + +/* + * Basic barrier test. + * + * N writers and 1 reader participate in the test. + * Each thread goes through M steps (=NB_STEPS). + * The main thread participates as the reader. + * + * A Step is divided in three parts: + * 1. before + * (barrier) + * 2. during + * (barrier) + * 3. after + * + * Each writer updates a thread-local variable with the + * current step number within part 2 and waits. + * + * The reader checks all variables during part 3, expecting + * all variables to be equal. If any variable differs, it means + * its thread was not properly blocked by the barrier. + */ +static void +test_barrier_basic(size_t n_threads) +{ + struct blocker_aux *aux; + pthread_t *threads; + size_t i; + + ovs_barrier_init(&barrier, n_threads + 1); + + aux = xcalloc(n_threads, sizeof *aux); + threads = xmalloc(n_threads * sizeof *threads); + for (i = 0; i < n_threads; i++) { + threads[i] = ovs_thread_create("ovs-barrier", + basic_blocker_main, &aux[i]); + } + + for (i = 0; i < NB_STEPS; i++) { + basic_block_check(aux, n_threads, i); + } + ovs_barrier_destroy(&barrier); + + for (i = 0; i < n_threads; i++) { + xpthread_join(threads[i], NULL); + } + + free(threads); + free(aux); +} + +static unsigned int *shared_mem; + +static void * +lead_blocker_main(void *aux_) +{ + struct blocker_aux *aux = aux_; + size_t i; + + aux->step = 0; + for (i = 0; i < NB_STEPS; i++) { + if (aux->leader) { + shared_mem = xmalloc(sizeof *shared_mem); + if (verbose) { + printf("*T1: allocated shmem\n"); + } + } + xnanosleep(random_range(100) * 1000); + + ovs_barrier_block(&barrier); + + if (verbose) { + printf("%cT%u: ENTER, writing\n", + (aux->leader ? '*' : ' '), aux->tid); + } + + shared_mem[0] = 42; + + ovs_barrier_block(&barrier); + + if (verbose) { + printf("%cT%u: EXIT\n", + (aux->leader ? '*' : ' '), aux->tid); + } + + if (aux->leader) { + free(shared_mem); + if (verbose) { + printf("*T1: freed shmem\n"); + } + } + xnanosleep(random_range(100) * 1000); + } + + return NULL; +} + +/* + * Leader barrier test. + * + * N threads participates, one of which is marked as + * the leader (thread 0). The main thread does not + * participate. + * + * The test is divided in M steps (=NB_STEPS). + * A Step is divided in three parts: + * 1. before + * (barrier) + * 2. during + * (barrier) + * 3. after + * + * Part 1, the leader allocates a block of shared memory. + * Part 2, all threads write to the shared memory. + * Part 3: the leader frees the shared memory. + * + * If any thread is improperly blocked by the barrier, + * the shared memory accesses will trigger a segfault + * or a use-after-free if ASAN is enabled. + */ +static void +test_barrier_lead(size_t n_threads) +{ + struct blocker_aux *aux; + pthread_t *threads; + size_t i; + + ovs_barrier_init(&barrier, n_threads); + + aux = xcalloc(n_threads, sizeof *aux); + threads = xmalloc(n_threads * sizeof *threads); + + aux[0].leader = true; + + for (i = 0; i < n_threads; i++) { + aux[i].tid = i + 1; + threads[i] = ovs_thread_create("ovs-barrier", + lead_blocker_main, &aux[i]); + } + + for (i = 0; i < n_threads; i++) { + xpthread_join(threads[i], NULL); + } + + /* If the main thread does not participate in the barrier, + * it must wait for all threads to join before destroying it. + */ + ovs_barrier_destroy(&barrier); + + free(threads); + free(aux); +} + +static void +usage(char *test_name) +{ + fprintf(stderr, "Usage: %s [n_threads=%d] [-v]\n", + test_name, DEFAULT_N_THREADS); +} + +static void +test_barrier(int argc, char *argv[]) +{ + size_t n_threads = DEFAULT_N_THREADS; + char **args = argv + optind - 1; + + set_program_name(argv[0]); + + argc -= optind; + if (argc > 2) { + usage(args[0]); + return; + } + + while (argc-- > 0) { + args++; + if (!strcmp(args[0], "-v")) { + verbose = true; + } else { + n_threads = strtol(args[0], NULL, 10); + if (n_threads > 20) { + n_threads = 20; + } + } + } + + test_barrier_basic(n_threads); + test_barrier_lead(n_threads); +} + +OVSTEST_REGISTER("test-barrier", test_barrier); -- 2.31.1 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev