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