[ovs-dev] [PATCH v4 03/27] tests: Add ovs-barrier unit test

Gaetan Rivet grive at u256.net
Wed Jun 9 13:09:11 UTC 2021


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 <grive at u256.net>
Reviewed-by: Maxime Coquelin <maxime.coquelin at redhat.com>
---
 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



More information about the dev mailing list