The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
atomic_ring_test.c
Go to the documentation of this file.
1/*
2 * atomic_ring_test.c Tests for the segmented SPSC atomic ring
3 *
4 * Version: $Id: bc358232c9a3e386653997a0745909b1a084cd60 $
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21RCSID("$Id: bc358232c9a3e386653997a0745909b1a084cd60 $")
22
23#include <freeradius-devel/io/atomic_queue.h>
24#include <freeradius-devel/util/debug.h>
25
26#include <inttypes.h>
27#include <pthread.h>
28#include <stdatomic.h>
29#include <stdint.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33
34/**********************************************************************/
35typedef struct request_s request_t;
36void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t *request);
37void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t *request) { }
38/**********************************************************************/
39
40#define TEST(_name) do { printf(" %-48s ", _name); fflush(stdout); } while (0)
41#define OK() do { printf("ok\n"); } while (0)
42#define FAIL(fmt, ...) do { \
43 printf("FAIL\n " fmt "\n", ##__VA_ARGS__); \
44 fr_exit_now(EXIT_FAILURE); \
45} while (0)
46
47#define CHECK(cond, fmt, ...) do { \
48 if (!(cond)) FAIL(fmt, ##__VA_ARGS__); \
49} while (0)
50
51/** 1. alloc/free round-trip with nothing ever pushed */
52static void test_alloc_free(TALLOC_CTX *ctx)
53{
54 fr_atomic_ring_t *ring;
55
56 TEST("alloc/free empty ring");
57
58 ring = fr_atomic_ring_alloc(ctx, 4);
59 CHECK(ring != NULL, "fr_atomic_ring_alloc returned NULL");
60
62 CHECK(ring == NULL, "fr_atomic_ring_free did not null the handle");
63 OK();
64}
65
66/** 2. push/pop within a single segment preserves FIFO */
67static void test_push_pop_single_segment(TALLOC_CTX *ctx)
68{
69 fr_atomic_ring_t *ring;
70 intptr_t i;
71 void *data;
72
73 TEST("push/pop within one segment preserves FIFO");
74
75 ring = fr_atomic_ring_alloc(ctx, 8);
76
77 for (i = 1; i <= 4; i++) {
78 CHECK(fr_atomic_ring_push(ring, (void *)i), "push %" PRIdPTR " failed", i);
79 }
80 for (i = 1; i <= 4; i++) {
81 CHECK(fr_atomic_ring_pop(ring, &data), "pop %" PRIdPTR " failed", i);
82 CHECK((intptr_t)data == i, "FIFO broken: expected %" PRIdPTR ", got %" PRIdPTR, i, (intptr_t)data);
83 }
84 CHECK(!fr_atomic_ring_pop(ring, &data), "pop from empty ring returned true");
85
87 OK();
88}
89
90/** 3. push past segment capacity triggers new-segment allocation */
91static void test_grow_across_segments(TALLOC_CTX *ctx)
92{
93 fr_atomic_ring_t *ring;
94 intptr_t i;
95 void *data;
96 size_t const seg_size = 4; /* power of 2 */
97 size_t const n = seg_size * 4; /* force 4 segments */
98
99 TEST("push past segment capacity grows transparently");
100
101 ring = fr_atomic_ring_alloc(ctx, seg_size);
102
103 for (i = 1; i <= (intptr_t)n; i++) {
104 CHECK(fr_atomic_ring_push(ring, (void *)i), "push %" PRIdPTR " failed", i);
105 }
106 for (i = 1; i <= (intptr_t)n; i++) {
107 CHECK(fr_atomic_ring_pop(ring, &data), "pop %" PRIdPTR " failed", i);
108 CHECK((intptr_t)data == i, "FIFO broken across segments: expected %" PRIdPTR ", got %" PRIdPTR,
109 i, (intptr_t)data);
110 }
111 CHECK(!fr_atomic_ring_pop(ring, &data), "pop from empty multi-segment ring returned true");
112
113 fr_atomic_ring_free(&ring);
114 OK();
115}
116
117/** 4. interleaved push/pop keeps FIFO and frees drained segments */
118static void test_interleaved(TALLOC_CTX *ctx)
119{
120 fr_atomic_ring_t *ring;
121 intptr_t push_seq = 1;
122 intptr_t pop_seq = 1;
123 void *data;
124 int round;
125
126 TEST("interleaved push/pop keeps FIFO across segment advances");
127
128 ring = fr_atomic_ring_alloc(ctx, 4);
129
130 /*
131 * Push batches larger than segment capacity so we cross
132 * segment boundaries, then drain. Many rounds to exercise
133 * repeated grow-and-retire cycles.
134 */
135 for (round = 0; round < 32; round++) {
136 int batch = (round % 5) + 6; /* 6..10 */
137 int i;
138
139 for (i = 0; i < batch; i++) {
140 CHECK(fr_atomic_ring_push(ring, (void *)push_seq),
141 "push %" PRIdPTR " failed at round %d", push_seq, round);
142 push_seq++;
143 }
144 for (i = 0; i < batch; i++) {
146 "pop %" PRIdPTR " failed at round %d", pop_seq, round);
147 CHECK((intptr_t)data == pop_seq,
148 "FIFO broken at round %d: expected %" PRIdPTR ", got %" PRIdPTR,
149 round, pop_seq, (intptr_t)data);
150 pop_seq++;
151 }
152 CHECK(!fr_atomic_ring_pop(ring, &data), "ring not empty after round %d", round);
153 }
154
155 fr_atomic_ring_free(&ring);
156 OK();
157}
158
159/** 5. free the ring with items still queued; must not leak/crash */
160static void test_free_nonempty(TALLOC_CTX *ctx)
161{
162 fr_atomic_ring_t *ring;
163 intptr_t i;
164
165 TEST("free releases all remaining segments");
166
167 ring = fr_atomic_ring_alloc(ctx, 4);
168
169 for (i = 1; i <= 32; i++) {
170 CHECK(fr_atomic_ring_push(ring, (void *)i), "push %" PRIdPTR " failed", i);
171 }
172
173 fr_atomic_ring_free(&ring);
174 OK();
175}
176
177
178/*
179 * Threaded stress test: one producer, one consumer, N items.
180 * Verifies that every pushed item is received in order.
181 */
182
183#define STRESS_N 200000
184
185typedef struct {
187 size_t n;
189
190static void *stress_producer(void *arg)
191{
192 stress_arg_t *sa = arg;
193 size_t i;
194
195 for (i = 1; i <= sa->n; i++) {
196 while (!fr_atomic_ring_push(sa->ring, (void *)(uintptr_t)i)) {
197 /*
198 * push can only fail on OOM in practice; spin so
199 * we notice stalls rather than miscounting.
200 */
201 sched_yield();
202 }
203 }
204 return NULL;
205}
206
207static void *stress_consumer(void *arg)
208{
209 stress_arg_t *sa = arg;
210 size_t expect = 1;
211 void *data;
212 uintptr_t *errp;
213
214 errp = malloc(sizeof(*errp));
215 *errp = 0;
216
217 while (expect <= sa->n) {
218 if (!fr_atomic_ring_pop(sa->ring, &data)) {
219 sched_yield();
220 continue;
221 }
222 if ((uintptr_t)data != expect) {
223 *errp = expect;
224 return errp;
225 }
226 expect++;
227 }
228
229 return errp; /* 0 on success */
230}
231
232static void test_stress_two_thread(TALLOC_CTX *ctx)
233{
234 fr_atomic_ring_t *ring;
235 pthread_t prod, cons;
236 stress_arg_t sa;
237 void *cons_ret;
238 uintptr_t err;
239 int rc;
240
241 TEST("producer/consumer stress - 200k items, 4-slot segments");
242
243 ring = fr_atomic_ring_alloc(ctx, 4);
244 sa.ring = ring;
245 sa.n = STRESS_N;
246
247 rc = pthread_create(&cons, NULL, stress_consumer, &sa);
248 CHECK(rc == 0, "pthread_create(consumer) failed: %s", strerror(rc));
249 rc = pthread_create(&prod, NULL, stress_producer, &sa);
250 CHECK(rc == 0, "pthread_create(producer) failed: %s", strerror(rc));
251
252 pthread_join(prod, NULL);
253 pthread_join(cons, &cons_ret);
254
255 err = *(uintptr_t *)cons_ret;
256 free(cons_ret);
257 CHECK(err == 0, "consumer saw out-of-order item at position %" PRIuPTR, err);
258
259 /*
260 * Drain anything the producer left behind (shouldn't be any
261 * at this point since consumer drained through sa.n).
262 */
263 {
264 void *data;
265 CHECK(!fr_atomic_ring_pop(ring, &data), "ring not empty after consumer finished");
266 }
267
268 fr_atomic_ring_free(&ring);
269 OK();
270}
271
272
273int main(int argc, UNUSED char *argv[])
274{
275 TALLOC_CTX *autofree = talloc_autofree_context();
276
277 if (argc > 1) {
278 fprintf(stderr, "usage: atomic_ring_test\n");
279 return EXIT_FAILURE;
280 }
281
282 printf("atomic_ring_test:\n");
283
290
291 printf(" all tests passed\n");
292 return EXIT_SUCCESS;
293}
int const char * file
Definition acutest.h:702
int n
Definition acutest.h:577
int const char int line
Definition acutest.h:702
bool fr_atomic_ring_push(fr_atomic_ring_t *ring, void *data)
Push a pointer into the ring; allocate a new segment on overflow.
void fr_atomic_ring_free(fr_atomic_ring_t **ring_p)
Free the ring and all remaining segments.
fr_atomic_ring_t * fr_atomic_ring_alloc(TALLOC_CTX *ctx, size_t seg_size)
Allocate an empty SPSC ring.
bool fr_atomic_ring_pop(fr_atomic_ring_t *ring, void **p_data)
Pop a pointer from the ring, advancing past drained segments.
static void * stress_producer(void *arg)
static void test_grow_across_segments(TALLOC_CTX *ctx)
static void test_interleaved(TALLOC_CTX *ctx)
static void test_free_nonempty(TALLOC_CTX *ctx)
static void * stress_consumer(void *arg)
#define OK()
int main(int argc, UNUSED char *argv[])
fr_atomic_ring_t * ring
#define CHECK(cond, fmt,...)
#define TEST(_name)
static void test_alloc_free(TALLOC_CTX *ctx)
#define STRESS_N
static void test_stress_two_thread(TALLOC_CTX *ctx)
static void test_push_pop_single_segment(TALLOC_CTX *ctx)
void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t *request)
static TALLOC_CTX * autofree
Definition fuzzer.c:44
#define RCSID(id)
Definition build.h:512
#define UNUSED
Definition build.h:336
static fr_slen_t err
Definition dict.h:882
free(array)
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:55
static fr_slen_t data
Definition value.h:1340