]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Unit test for RCU
authorMaria Matejka <mq@ucw.cz>
Sun, 2 Jun 2024 20:49:35 +0000 (22:49 +0200)
committerMaria Matejka <mq@ucw.cz>
Tue, 4 Jun 2024 08:11:36 +0000 (10:11 +0200)
lib/Makefile
lib/rcu_test.c [new file with mode: 0644]

index 5c106e2a4905139553ce30c553f20e59ee85d14c..1a2cc92861f15ba84db60f398d0c3638d9c47e4d 100644 (file)
@@ -2,6 +2,6 @@ src := a-path.c a-set.c bitmap.c bitops.c blake2s.c blake2b.c checksum.c defer.c
 obj := $(src-o-files)
 $(all-daemon)
 
-tests_src := a-set_test.c a-path_test.c attribute_cleanup_test.c bitmap_test.c heap_test.c buffer_test.c event_test.c flowspec_test.c bitops_test.c patmatch_test.c fletcher16_test.c slist_test.c checksum_test.c lists_test.c locking_test.c mac_test.c ip_test.c hash_test.c printf_test.c slab_test.c tlists_test.c type_test.c
+tests_src := a-set_test.c a-path_test.c attribute_cleanup_test.c bitmap_test.c heap_test.c buffer_test.c event_test.c flowspec_test.c bitops_test.c patmatch_test.c fletcher16_test.c slist_test.c checksum_test.c lists_test.c locking_test.c mac_test.c ip_test.c hash_test.c printf_test.c rcu_test.c slab_test.c tlists_test.c type_test.c
 tests_targets := $(tests_targets) $(tests-target-files)
 tests_objs := $(tests_objs) $(src-o-files)
diff --git a/lib/rcu_test.c b/lib/rcu_test.c
new file mode 100644 (file)
index 0000000..b837213
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ *     BIRD Library -- Auto storage attribute cleanup test
+ *
+ *     (c) 2023 Maria Matejka <mq@jmq.cz>
+ *     (c) 2023 CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "test/birdtest.h"
+
+#include "lib/rcu.h"
+#include "lib/io-loop.h"
+
+#include <pthread.h>
+
+#define WRITERS                3
+#define READERS                28
+
+#define WRITER_ROUNDS  20
+
+static struct block {
+  struct block * _Atomic next;
+  u64 value;
+} ball[WRITERS][WRITER_ROUNDS];
+
+static struct block *_Atomic bin;
+static _Atomic uint seen = 0;
+
+_Thread_local struct rcu_thread rtl;
+
+static void *
+t_rcu_basic_reader(void *_ UNUSED)
+{
+  rcu_thread_start(&rtl);
+
+  while (atomic_load_explicit(&bin, memory_order_acquire) == NULL)
+    birdloop_yield();
+
+  atomic_fetch_add_explicit(&seen, 1, memory_order_release);
+
+  while (atomic_load_explicit(&bin, memory_order_acquire))
+  {
+    rcu_read_lock();
+
+    uint mod = 0;
+    for (struct block * _Atomic *bp = &bin, *b;
+       b = atomic_load_explicit(bp, memory_order_acquire);
+       bp = &b->next)
+    {
+      uint val = b->value % WRITERS + 1;
+      ASSERT_DIE(val > mod);
+      mod = val;
+    }
+
+    ASSERT_DIE(mod <= WRITERS);
+
+    rcu_read_unlock();
+  }
+
+  rcu_thread_stop(&rtl);
+  return NULL;
+}
+
+static _Atomic uint spinlock = 0;
+
+static inline void
+spin_lock(void)
+{
+  while (atomic_exchange_explicit(&spinlock, 1, memory_order_acq_rel))
+    birdloop_yield();
+}
+
+static inline void
+spin_unlock(void)
+{
+  ASSERT_DIE(atomic_exchange_explicit(&spinlock, 0, memory_order_acq_rel));
+}
+
+static void *
+t_rcu_basic_writer(void *order_ptr)
+{
+  rcu_thread_start(&rtl);
+
+  uint order = (uintptr_t) order_ptr;
+  struct block *cur = &ball[order][0];
+
+  /* Insert the object */
+  spin_lock();
+  for (struct block * _Atomic *bp = &bin; bp; )
+  {
+    struct block *b = atomic_load_explicit(bp, memory_order_acquire);
+    if (b && ((b->value % WRITERS) < order))
+      bp = &b->next;
+    else
+    {
+      ASSERT_DIE(cur->value == 0xbabababababababa);
+      cur->value = order;
+      atomic_store_explicit(&cur->next, b, memory_order_relaxed);
+      atomic_store_explicit(bp, cur, memory_order_release);
+      break;
+    }
+  }
+  spin_unlock();
+
+  /* Wait for readers */
+  while (atomic_load_explicit(&seen, memory_order_acquire) != READERS)
+    birdloop_yield();
+
+  /* Update the object */
+  for (uint i=1; i<WRITER_ROUNDS; i++)
+  {
+    struct block *next = &ball[order][i];
+    ASSERT_DIE(next->value == 0xbabababababababa);
+    next->value = order + i*WRITERS;
+
+    spin_lock();
+    _Bool seen = 0;
+    for (struct block * _Atomic *bp = &bin, *b;
+       b = atomic_load_explicit(bp, memory_order_acquire);
+       bp = &b->next)
+      if (b == cur)
+      {
+       struct block *link = atomic_load_explicit(&b->next, memory_order_relaxed);
+       atomic_store_explicit(&next->next, link, memory_order_relaxed);
+       atomic_store_explicit(bp, next, memory_order_release);
+       seen = 1;
+       break;
+      }
+    ASSERT_DIE(seen);
+    spin_unlock();
+
+    synchronize_rcu();
+
+    ASSERT_DIE(cur->value + WRITERS == next->value);
+    cur->value = 0xd4d4d4d4d4d4d4d4;
+    atomic_store_explicit(&cur->next, ((void *) 0xd8d8d8d8d8d8d8d8), memory_order_relaxed);
+
+    cur = next;
+  }
+
+  /* Remove the object */
+  spin_lock();
+  _Bool seen = 0;
+  for (struct block * _Atomic *bp = &bin, *b;
+      b = atomic_load_explicit(bp, memory_order_acquire);
+      bp = &b->next)
+    if (b == cur)
+    {
+      struct block *link = atomic_load_explicit(&b->next, memory_order_relaxed);
+      atomic_store_explicit(bp, link, memory_order_relaxed);
+      seen = 1;
+      break;
+    }
+  ASSERT_DIE(seen);
+  spin_unlock();
+
+  synchronize_rcu();
+
+  cur->value = 0xd4d4d4d4d4d4d4d4;
+  atomic_store_explicit(&cur->next, ((void *) 0xd8d8d8d8d8d8d8d8), memory_order_relaxed);
+
+  rcu_thread_stop(&rtl);
+  return NULL;
+}
+
+static int
+t_rcu_basic(void)
+{
+  memset(ball, 0xba, sizeof ball);
+
+  pthread_t readers[READERS];
+  pthread_t writers[WRITERS];
+
+  for (uint i=0; i<READERS; i++)
+    pthread_create(&readers[i], NULL, t_rcu_basic_reader, NULL);
+
+  for (uintptr_t i=0; i<WRITERS; i++)
+    pthread_create(&writers[i], NULL, t_rcu_basic_writer, (void *) i);
+
+  for (uintptr_t i=0; i<WRITERS; i++)
+    pthread_join(writers[i], NULL);
+
+  for (uintptr_t i=0; i<READERS; i++)
+    pthread_join(readers[i], NULL);
+
+  for (uint w = 0; w < WRITERS; w++)
+    for (uint r = 0; r < WRITER_ROUNDS; r++)
+    {
+      ASSERT_DIE(ball[w][r].value == 0xd4d4d4d4d4d4d4d4);
+      ASSERT_DIE(atomic_load_explicit(&ball[w][r].next, memory_order_relaxed) == (void *) 0xd8d8d8d8d8d8d8d8);
+    }
+
+  return 1;
+}
+
+int main(int argc, char **argv)
+{
+  bt_init(argc, argv);
+
+  bt_test_suite(t_rcu_basic, "Basic RCU check");
+
+  return bt_exit_value();
+}