The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
atomic_queue_test.c
Go to the documentation of this file.
1/*
2 * atomic_queue_test.c Tests for atomic queues
3 *
4 * Version: $Id: 999e6f7dc8394101520fde4c875ff0b4a99d0327 $
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 * @copyright 2016 Alan DeKok (aland@freeradius.org)
21 */
22
23RCSID("$Id: 999e6f7dc8394101520fde4c875ff0b4a99d0327 $")
24
25#include <freeradius-devel/io/atomic_queue.h>
26#include <freeradius-devel/util/debug.h>
27#include <freeradius-devel/util/table.h>
28#include <freeradius-devel/util/strerror.h>
29#include <string.h>
30#include <sys/time.h>
31
32#ifdef HAVE_GETOPT_H
33# include <getopt.h>
34#endif
35
36#define OFFSET (1024)
37
38static int debug_lvl = 0;
39
40/** Which allocator the queue under test comes from
41 */
42typedef enum : uint8_t {
43 QUEUE_TYPE_TALLOC = 0, //!< #fr_atomic_queue_talloc, queue lives in the talloc hierarchy.
44 QUEUE_TYPE_MALLOC, //!< #fr_atomic_queue_malloc, raw queue owned by a talloc handle.
45 QUEUE_TYPE_MMAP, //!< #fr_atomic_queue_mmap, shared memory queue owned by a talloc handle.
48
50 { L("malloc"), QUEUE_TYPE_MALLOC },
51 { L("mmap"), QUEUE_TYPE_MMAP },
52 { L("talloc"), QUEUE_TYPE_TALLOC }
53};
55
56
57/**********************************************************************/
58typedef struct request_s request_t;
59void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t *request);
60
62{
63}
64/**********************************************************************/
65
66
67static NEVER_RETURNS void usage(void)
68{
69 fprintf(stderr, "usage: atomic_queue_test [OPTS]\n");
70 fprintf(stderr, " -s size set queue size.\n");
71 fprintf(stderr, " -t type queue type to test, 'talloc', 'malloc' or 'mmap' (default: all).\n");
72 fprintf(stderr, " -x Debugging mode.\n");
73
74 fr_exit_now(EXIT_SUCCESS);
75}
76
77/** Fill the queue to capacity, check it refuses one more, then drain it
78 *
79 * Exits the process on the first wrong answer.
80 */
81static void queue_exercise_flavour(fr_atomic_queue_t *aq, int size,
82 bool (*push)(fr_atomic_queue_t *, void const *),
83 bool (*pop)(fr_atomic_queue_t *, void *))
84{
85 int i;
86 intptr_t val;
87 void *data;
88
89#ifndef NDEBUG
90 if (debug_lvl) {
91 printf("Start\n");
92 fr_atomic_queue_debug(stdout, aq);
93
94 if (debug_lvl > 1) printf("Filling with %d\n", size);
95 }
96
97#endif
98
99
100 for (i = 0; i < size; i++) {
101 val = i + OFFSET;
102 data = (void *) val;
103
104 if (!push(aq, &data)) {
105 fprintf(stderr, "Failed pushing at %d\n", i);
106 fr_exit_now(EXIT_FAILURE);
107 }
108
109#ifndef NDEBUG
110 if (debug_lvl > 1) {
111 printf("iteration %d\n", i);
112 fr_atomic_queue_debug(stdout, aq);
113 }
114#endif
115 }
116
117 val = size + OFFSET;
118 data = (void *) val;
119
120 /*
121 * Queue is full. No more pushes are allowed.
122 */
123 if (push(aq, &data)) {
124 fprintf(stderr, "Pushed an entry past the end of the queue.");
125 fr_exit_now(EXIT_FAILURE);
126 }
127
128#ifndef NDEBUG
129 if (debug_lvl) {
130 printf("Full\n");
131 fr_atomic_queue_debug(stdout, aq);
132
133 if (debug_lvl > 1) printf("Emptying\n");
134 }
135#endif
136
137 /*
138 * And now pop them all.
139 */
140 for (i = 0; i < size; i++) {
141 if (!pop(aq, &data)) {
142 fprintf(stderr, "Failed popping at %d\n", i);
143 fr_exit_now(EXIT_FAILURE);
144 }
145
146 val = (intptr_t) data;
147 if (val != (i + OFFSET)) {
148 fprintf(stderr, "Pop expected %d, got %d\n",
149 i + OFFSET, (int) val);
150 fr_exit_now(EXIT_FAILURE);
151 }
152
153#ifndef NDEBUG
154 if (debug_lvl > 1) {
155 printf("iteration %d\n", i);
156 fr_atomic_queue_debug(stdout, aq);
157 }
158#endif
159 }
160
161 /*
162 * Queue is empty. No more pops are allowed.
163 */
164 if (pop(aq, &data)) {
165 fprintf(stderr, "Popped an entry past the end of the queue.");
166 fr_exit_now(EXIT_FAILURE);
167 }
168
169#ifndef NDEBUG
170 if (debug_lvl) {
171 printf("Empty\n");
172 fr_atomic_queue_debug(stdout, aq);
173 }
174#endif
175}
176
177/** Fill and drain with the many-producer functions, then the single-producer ones
178 */
184
185/** Queue allocated inside the talloc hierarchy
186 */
187static void test_talloc(TALLOC_CTX *ctx, int size)
188{
190
191 aq = fr_atomic_queue_talloc(ctx, size, sizeof(uintptr_t));
192 if (!aq) {
193 fprintf(stderr, "Failed allocating talloc queue\n");
194 fr_exit_now(EXIT_FAILURE);
195 }
196
197 queue_exercise(aq, size);
199}
200
201/** Queue allocated outside talloc, but owned by a talloc context
202 *
203 * The only talloc'd block is the owner handle, so the context holds
204 * exactly one child, and freeing the context releases the raw queue
205 * through the handle.
206 */
207static void test_malloc(TALLOC_CTX *ctx, int size)
208{
210 TALLOC_CTX *owner_ctx;
211
212 owner_ctx = talloc_new(ctx);
213 aq = fr_atomic_queue_malloc(owner_ctx, size, sizeof(uintptr_t));
214 if (!aq) {
215 fprintf(stderr, "Failed allocating raw queue\n");
216 fr_exit_now(EXIT_FAILURE);
217 }
218
219 if (talloc_total_blocks(owner_ctx) != 2) {
220 fprintf(stderr, "Expected owner handle to be the only child of ctx, found %zu blocks\n",
221 talloc_total_blocks(owner_ctx));
222 fr_exit_now(EXIT_FAILURE);
223 }
224
225 queue_exercise(aq, size);
226 talloc_free(owner_ctx);
227}
228
229/** Queue in a shared memory mapping, owned by a talloc handle
230 *
231 * As test_malloc for the creator. Then a second queue is attached to
232 * the creator's descriptor, as another process would, and elements
233 * pushed through one are popped from the other.
234 */
235static void test_mmap(TALLOC_CTX *ctx, int size)
236{
237 fr_atomic_queue_t *aq, *peer;
238 TALLOC_CTX *owner_ctx, *peer_ctx;
239 int fd = -1, peer_fd, bad_fd, i;
240 intptr_t val;
241 void *data;
242
243 owner_ctx = talloc_new(ctx);
244 aq = fr_atomic_queue_mmap(owner_ctx, &fd, size, sizeof(uintptr_t));
245 if (!aq) {
246 fprintf(stderr, "Failed allocating mapped queue: %s\n", fr_strerror());
247 fr_exit_now(EXIT_FAILURE);
248 }
249
250 if ((talloc_total_blocks(owner_ctx) != 2) || (fd < 0)) {
251 fprintf(stderr, "Expected an owner handle as the only child of ctx and a descriptor, "
252 "found %zu blocks, fd %d\n", talloc_total_blocks(owner_ctx), fd);
253 fr_exit_now(EXIT_FAILURE);
254 }
255
256 queue_exercise(aq, size);
257
258 /*
259 * Attach as a second process would, with its own descriptor.
260 */
261 peer_fd = dup(fd);
262 if (peer_fd < 0) {
263 fprintf(stderr, "Failed duplicating queue descriptor\n");
264 fr_exit_now(EXIT_FAILURE);
265 }
266
267 peer_ctx = talloc_new(ctx);
268 peer = fr_atomic_queue_mmap(peer_ctx, &peer_fd, size, sizeof(uintptr_t));
269 if (!peer) {
270 fprintf(stderr, "Failed attaching to mapped queue: %s\n", fr_strerror());
271 fr_exit_now(EXIT_FAILURE);
272 }
273
274 bad_fd = dup(fd);
275 if (fr_atomic_queue_mmap(peer_ctx, &bad_fd, size, sizeof(uintptr_t) * 2)) {
276 fprintf(stderr, "Attached to a mapped queue with the wrong element size\n");
277 fr_exit_now(EXIT_FAILURE);
278 }
279 close(bad_fd);
280
281 for (i = 0; i < size; i++) {
282 val = i + OFFSET;
283 data = (void *) val;
284 if (!fr_atomic_queue_spsc_push(aq, &data)) {
285 fprintf(stderr, "Failed pushing to shared queue at %d\n", i);
286 fr_exit_now(EXIT_FAILURE);
287 }
288 }
289
290 for (i = 0; i < size; i++) {
291 if (!fr_atomic_queue_spsc_pop(peer, &data)) {
292 fprintf(stderr, "Failed popping from the attached queue at %d\n", i);
293 fr_exit_now(EXIT_FAILURE);
294 }
295 val = (intptr_t) data;
296 if (val != (i + OFFSET)) {
297 fprintf(stderr, "Attached queue returned %" PRIdPTR " at %d, expected %" PRIdPTR "\n",
298 val, i, (intptr_t) (i + OFFSET));
299 fr_exit_now(EXIT_FAILURE);
300 }
301 }
302
303 /*
304 * The creator going away leaves the attached mapping valid.
305 */
306 talloc_free(owner_ctx);
307 val = OFFSET;
308 data = (void *) val;
309 if (!fr_atomic_queue_spsc_push(peer, &data) ||
311 ((intptr_t) data != OFFSET)) {
312 fprintf(stderr, "Attached queue unusable after the creator freed its mapping\n");
313 fr_exit_now(EXIT_FAILURE);
314 }
315 talloc_free(peer_ctx);
316}
317
318typedef void (*queue_test_t)(TALLOC_CTX *ctx, int size);
319
325
326int main(int argc, char *argv[])
327{
328 int c;
329 int size;
330 int type;
331 bool selected[QUEUE_TYPE_MAX] = {};
332 bool any_selected = false;
333 TALLOC_CTX *autofree = talloc_autofree_context();
334
335 size = 4;
336
337 while ((c = getopt(argc, argv, "hs:t:x")) != -1) switch (c) {
338 case 's':
339 size = atoi(optarg);
340 break;
341
342 case 't':
344 if (type < 0) {
345 fprintf(stderr, "Unknown queue type '%s'\n", optarg);
346 usage();
347 }
348 selected[type] = true;
349 any_selected = true;
350 break;
351
352 case 'x':
353 debug_lvl++;
354 break;
355
356 case 'h':
357 default:
358 usage();
359 }
360#if 0
361 argc -= (optind - 1);
362 argv += (optind - 1);
363#endif
364
365 for (type = 0; type < QUEUE_TYPE_MAX; type++) {
366 if (any_selected && !selected[type]) continue;
367
368 if (debug_lvl) printf("Testing %s queue\n", fr_table_str_by_value(queue_type_table, type, "<INVALID>"));
369 queue_tests[type](autofree, size);
370 }
371
372 return 0;
373}
374
void int line
Definition acutest.h:810
void fr_atomic_queue_debug(FILE *fp, fr_atomic_queue_t *aq)
Dump an atomic queue.
fr_atomic_queue_t * fr_atomic_queue_malloc(TALLOC_CTX *ctx, size_t num, size_t elem_len)
Create fixed-size atomic queue outside the talloc hierarchy, owned by a talloc context.
bool fr_atomic_queue_mpmc_pop(fr_atomic_queue_t *aq, void *data)
Copy an element out of the atomic queue, any number of consumers.
fr_atomic_queue_t * fr_atomic_queue_mmap(TALLOC_CTX *ctx, int *fd, size_t num, size_t elem_len)
Create or attach to a fixed-size atomic queue in shared memory, owned by a talloc context.
bool fr_atomic_queue_mpmc_push(fr_atomic_queue_t *aq, void const *data)
Copy an element into the atomic queue, any number of producers.
void fr_atomic_queue_free(fr_atomic_queue_t **aq)
Free an atomic queue if it's not freed by ctx.
bool fr_atomic_queue_spsc_push(fr_atomic_queue_t *aq, void const *data)
Copy an element into the atomic queue, one producer only.
fr_atomic_queue_t * fr_atomic_queue_talloc(TALLOC_CTX *ctx, size_t num, size_t elem_len)
Create a fixed-size atomic queue.
bool fr_atomic_queue_spsc_pop(fr_atomic_queue_t *aq, void *data)
Copy an element out of the atomic queue, one consumer only.
Structure to hold the atomic queue.
int main(int argc, char *argv[])
#define OFFSET
static void test_malloc(TALLOC_CTX *ctx, int size)
Queue allocated outside talloc, but owned by a talloc context.
static void queue_exercise_flavour(fr_atomic_queue_t *aq, int size, bool(*push)(fr_atomic_queue_t *, void const *), bool(*pop)(fr_atomic_queue_t *, void *))
Fill the queue to capacity, check it refuses one more, then drain it.
static fr_table_num_sorted_t const queue_type_table[]
static void queue_exercise(fr_atomic_queue_t *aq, int size)
Fill and drain with the many-producer functions, then the single-producer ones.
static size_t queue_type_table_len
void(* queue_test_t)(TALLOC_CTX *ctx, int size)
static void test_mmap(TALLOC_CTX *ctx, int size)
Queue in a shared memory mapping, owned by a talloc handle.
static queue_test_t const queue_tests[QUEUE_TYPE_MAX]
static void test_talloc(TALLOC_CTX *ctx, int size)
Queue allocated inside the talloc hierarchy.
queue_type_t
Which allocator the queue under test comes from.
@ QUEUE_TYPE_MAX
@ QUEUE_TYPE_MALLOC
fr_atomic_queue_malloc, raw queue owned by a talloc handle.
@ QUEUE_TYPE_MMAP
fr_atomic_queue_mmap, shared memory queue owned by a talloc handle.
@ QUEUE_TYPE_TALLOC
fr_atomic_queue_talloc, queue lives in the talloc hierarchy.
static NEVER_RETURNS void usage(void)
void request_verify(UNUSED char const *file, UNUSED int line, UNUSED request_t *request)
static int debug_lvl
static uint8_t request[4]
#define RCSID(id)
Definition build.h:603
#define NEVER_RETURNS
Should be placed before the function return type.
Definition build.h:413
#define L(_str)
Helper for initialising arrays of string literals.
Definition build.h:245
#define UNUSED
Definition build.h:415
#define NUM_ELEMENTS(_t)
Definition build.h:437
TALLOC_CTX * autofree
Definition common.c:29
#define fr_exit_now(_x)
Exit without calling atexit() handlers, producing a log message in debug builds.
Definition debug.h:272
talloc_free(hp)
unsigned char uint8_t
fr_aka_sim_id_type_t type
#define fr_table_value_by_str(_table, _name, _def)
Convert a string to a value using a sorted or ordered table.
Definition table.h:685
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:804
An element in a lexicographically sorted array of name to num mappings.
Definition table.h:49
#define talloc_autofree_context
The original function is deprecated, so replace it with our version.
Definition talloc.h:55
char const * fr_strerror(void)
Get the last library error.
Definition strerror.c:558
static fr_slen_t data
Definition value.h:1366