@cryptotaxi247 / netdata / commits / ecf85b536

Add deterministic unit tests for rw-spinlock API (#22659)

Add deterministic unit tests for rw-spinlock API to validate basic semantics and integrate them into the test suite.

Stelios Fragkakis committed Jun 8, 2026 at 16:00 UTC ecf85b536835edb20ab6a1c00449ab1f51872f6e
4 files changed +131
CMakeLists.txt
+1
@@ -1191,6 +1191,7 @@ set(LIBNETDATA_FILES
1191 src/libnetdata/locks/spinlock.h
1192 src/libnetdata/locks/rw-spinlock.c
1193 src/libnetdata/locks/rw-spinlock.h
1194 + src/libnetdata/locks/rw-spinlock-unittest.c
1195 src/libnetdata/atomics/atomic_flags.h
1196 src/libnetdata/atomics/atomics.h
1197 src/libnetdata/locks/waitq.c
src/daemon/main.c
+5
@@ -460,6 +460,7 @@ int netdata_main(int argc, char **argv) {
460 if (yaml_unittest()) return 1;
461 if (json_c_parser_unittest()) return 1;
462 if (unittest_waiting_queue()) return 1;
463 + if (rw_spinlock_unittest()) return 1;
464 if (uuidmap_unittest()) return 1;
465 #ifdef HAVE_LIBBACKTRACE
466 if (stacktrace_unittest()) return 1;
@@ -513,6 +514,10 @@ int netdata_main(int argc, char **argv) {
514 unittest_running = true;
515 return rwlocks_stress_test();
516 }
517 + else if(strcmp(optarg, "rwspinlocktest") == 0) {
518 + unittest_running = true;
519 + return rw_spinlock_unittest();
520 + }
521 else if(strcmp(optarg, "prd-array-stress") == 0) {
522 unittest_running = true;
523 return prd_array_stress_test();
src/libnetdata/locks/rw-spinlock-unittest.c new
+123
@@ -0,0 +1,123 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "libnetdata/libnetdata.h"
4 +
5 +// Deterministic unit tests for the baseline rw-spinlock API semantics that
6 +// already hold on master. These tests intentionally avoid timing/sleep-based
7 +// concurrency assertions (kept in the rwlockstest benchmark) so they stay
8 +// reproducible. They also intentionally do NOT assert writer-priority,
9 +// writer-starvation prevention, or writer-pending reader blocking; those
10 +// behaviors belong with the PR that introduces them.
11 +
12 +#define RW_TEST(condition, msg) do { \
13 + if (!(condition)) { \
14 + fprintf(stderr, "rw-spinlock unittest FAILED: %s (%s:%d)\n", \
15 + (msg), __FUNCTION__, __LINE__); \
16 + errors++; \
17 + } \
18 + } while(0)
19 +
20 +int rw_spinlock_unittest(void) {
21 + int errors = 0;
22 +
23 + fprintf(stderr, "\nrunning rw-spinlock unittest\n");
24 +
25 + // ----------------------------------------------------------------------
26 + // initialization: a freshly initialized lock has no writer and no readers,
27 + // and a static initializer produces the same state.
28 + {
29 + RW_SPINLOCK lock;
30 + rw_spinlock_init(&lock);
31 + RW_TEST(lock.writer == 0, "init clears writer");
32 + RW_TEST(lock.counter == 0, "init clears counter");
33 +
34 + RW_SPINLOCK lock_static = RW_SPINLOCK_INITIALIZER;
35 + RW_TEST(lock_static.writer == 0, "static initializer clears writer");
36 + RW_TEST(lock_static.counter == 0, "static initializer clears counter");
37 + }
38 +
39 + // ----------------------------------------------------------------------
40 + // tryread on a free lock succeeds, and read_unlock restores the free state.
41 + {
42 + RW_SPINLOCK lock = RW_SPINLOCK_INITIALIZER;
43 + RW_TEST(rw_spinlock_tryread_lock(&lock) == true, "tryread succeeds on free lock");
44 + rw_spinlock_read_unlock(&lock);
45 + RW_TEST(lock.counter == 0, "read_unlock restores free state");
46 + }
47 +
48 + // ----------------------------------------------------------------------
49 + // concurrent (and recursive) readers: a shared lock admits multiple
50 + // simultaneous read holders. With no writer held or pending, the same
51 + // thread can take the read lock repeatedly; each acquisition succeeds and
52 + // each unlock is balanced.
53 + {
54 + RW_SPINLOCK lock = RW_SPINLOCK_INITIALIZER;
55 + const int readers = 5;
56 +
57 + for (int i = 0; i < readers; i++)
58 + RW_TEST(rw_spinlock_tryread_lock(&lock) == true, "additional reader admitted");
59 +
60 + // a writer must not be able to enter while readers are present
61 + RW_TEST(rw_spinlock_trywrite_lock(&lock) == false, "trywrite fails while readers held");
62 +
63 + for (int i = 0; i < readers; i++)
64 + rw_spinlock_read_unlock(&lock);
65 +
66 + RW_TEST(lock.counter == 0, "all readers released");
67 +
68 + // once readers are gone, a writer can enter
69 + RW_TEST(rw_spinlock_trywrite_lock(&lock) == true, "trywrite succeeds after readers drain");
70 + rw_spinlock_write_unlock(&lock);
71 + RW_TEST(lock.counter == 0, "write_unlock restores free state");
72 + }
73 +
74 + // ----------------------------------------------------------------------
75 + // blocking read_lock path (non-contended): read_lock/read_unlock balance
76 + // and admit multiple holders just like tryread.
77 + {
78 + RW_SPINLOCK lock = RW_SPINLOCK_INITIALIZER;
79 + rw_spinlock_read_lock(&lock);
80 + rw_spinlock_read_lock(&lock);
81 + RW_TEST(rw_spinlock_trywrite_lock(&lock) == false, "trywrite fails while blocking readers held");
82 + rw_spinlock_read_unlock(&lock);
83 + rw_spinlock_read_unlock(&lock);
84 + RW_TEST(lock.counter == 0, "blocking readers released");
85 + }
86 +
87 + // ----------------------------------------------------------------------
88 + // trywrite on a free lock succeeds and records the writer; write_unlock
89 + // clears the writer and the writer bit.
90 + {
91 + RW_SPINLOCK lock = RW_SPINLOCK_INITIALIZER;
92 + RW_TEST(rw_spinlock_trywrite_lock(&lock) == true, "trywrite succeeds on free lock");
93 + RW_TEST(lock.writer == gettid_cached(), "trywrite records the writer tid");
94 + rw_spinlock_write_unlock(&lock);
95 + RW_TEST(lock.writer == 0, "write_unlock clears writer");
96 + RW_TEST(lock.counter == 0, "write_unlock restores free state");
97 + }
98 +
99 + // ----------------------------------------------------------------------
100 + // writer exclusivity: while a writer holds the lock, neither a reader nor
101 + // another writer may enter.
102 + {
103 + RW_SPINLOCK lock = RW_SPINLOCK_INITIALIZER;
104 + rw_spinlock_write_lock(&lock);
105 + RW_TEST(rw_spinlock_tryread_lock(&lock) == false, "tryread fails while writer held");
106 + RW_TEST(rw_spinlock_trywrite_lock(&lock) == false, "second trywrite fails while writer held");
107 + rw_spinlock_write_unlock(&lock);
108 +
109 + // after release both a reader and a writer can enter again
110 + RW_TEST(rw_spinlock_tryread_lock(&lock) == true, "tryread succeeds after writer released");
111 + rw_spinlock_read_unlock(&lock);
112 + RW_TEST(rw_spinlock_trywrite_lock(&lock) == true, "trywrite succeeds after writer released");
113 + rw_spinlock_write_unlock(&lock);
114 + RW_TEST(lock.counter == 0, "lock free after exclusivity test");
115 + }
116 +
117 + if (errors)
118 + fprintf(stderr, "rw-spinlock unittest: %d ERROR(S)\n", errors);
119 + else
120 + fprintf(stderr, "rw-spinlock unittest: OK\n");
121 +
122 + return errors;
123 +}
src/libnetdata/locks/rw-spinlock.h
+2
@@ -30,4 +30,6 @@ bool rw_spinlock_trywrite_lock_with_trace(RW_SPINLOCK *rw_spinlock, const char *
30 #define rw_spinlock_tryread_lock(rw_spinlock) rw_spinlock_tryread_lock_with_trace(rw_spinlock, __FUNCTION__)
31 #define rw_spinlock_trywrite_lock(rw_spinlock) rw_spinlock_trywrite_lock_with_trace(rw_spinlock, __FUNCTION__)
32
33 +int rw_spinlock_unittest(void);
34 +
35 #endif //NETDATA_RW_SPINLOCK_H