The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
slab_tests.c
Go to the documentation of this file.
1/** Tests for slab allocator
2 *
3 * @file src/lib/util/test//slab_tests.c
4 *
5 * @copyright 2023 Network RADIUS SAS (legal@networkradius.com)
6 */
7#include "acutest.h"
8#include"acutest_helpers.h"
9#include <freeradius-devel/util/timer.h>
10#include <freeradius-devel/util/slab.h>
11
12typedef struct {
13 int num;
14 char *name;
16
17typedef struct {
18 int count;
20
21typedef struct {
24
27 .min_elements = 1,
28 .max_elements = 4,
29 .at_max_fail = false,
30 .num_children = 0,
31 .child_pool_size = 0,
32 .interval.value = 1 * NSEC
33};
34
35static int test_element_free(test_element_t *elem, void *uctx)
36{
37 test_uctx_t *test_uctx = uctx;
38 test_uctx->count = strlen(elem->name);
39 return 0;
40}
41
43static fr_time_t test_time(void)
44{
45 return test_time_base;
46}
47
50
51/** Test basic allocation and reservation of elements
52 *
53 */
54static void test_alloc(void)
55{
56 test_slab_list_t *test_slab_list;
57 test_element_t *test_elements[5];
58 test_uctx_t test_uctx, test_uctx2;
59
60 /*
61 * Each slab will contain 2 elements, maximum of 4 elements allocated from slabs.
62 */
63 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, true, false);
64 TEST_ASSERT(test_slab_list != NULL);
65
66 test_elements[0] = test_slab_reserve(test_slab_list);
67 TEST_CHECK(test_elements[0] != NULL);
68 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
69 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
70
71 test_uctx.count = 0;
72 test_uctx2.count = 0;
73
74 /* "if" to keep clang scan happy */
75 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
76 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
77
78 test_elements[1] = test_slab_reserve(test_slab_list);
79 TEST_CHECK(test_elements[1] != NULL);
80 TEST_CHECK(test_elements[1] != test_elements[0]);
81 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
82 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
83
84 /* This will cause a second slab to be allocated */
85 test_elements[2] = test_slab_reserve(test_slab_list);
86 TEST_CHECK(test_elements[2] != NULL);
87 if (test_elements[2]) test_elements[2]->name = talloc_strdup(test_elements[2], "Hello there testing");
88 if (test_elements[2]) test_slab_element_set_destructor(test_elements[2], test_element_free, &test_uctx2);
89 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
90 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 3);
91
92 test_elements[3] = test_slab_reserve(test_slab_list);
93 TEST_CHECK(test_elements[3] != NULL);
94 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
95 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
96
97 /* This is more elements than max_elements */
98 test_elements[4] = test_slab_reserve(test_slab_list);
99 TEST_CHECK(test_elements[4] != NULL);
100 /* Allocations beyond the maximum do not amend the slab stats */
101 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
102 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
103
104 if (test_elements[0]) test_slab_release(test_elements[0]);
105 TEST_CHECK(test_uctx.count == 11);
106 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
107 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 3);
108
109 if (test_elements[1]) test_slab_release(test_elements[1]);
110 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
111 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
112
113 if (test_elements[2]) test_slab_release(test_elements[2]);
114 TEST_CHECK(test_uctx2.count == 19);
115 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
116 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
117
118 talloc_free(test_slab_list);
119}
120
121/** Test allocation beyond max fails correctly
122 *
123 */
124static void test_alloc_fail(void)
125{
126 test_slab_list_t *test_slab_list;
127 test_element_t *test_elements[5];
128 fr_slab_config_t slab_config = def_slab_config;
129
130 /*
131 * Each slab will contain 2 elements, maximum of 4 elements allocated from slabs.
132 */
133 slab_config.at_max_fail = true;
134 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
135 TEST_ASSERT(test_slab_list != NULL);
136
137 test_elements[0] = test_slab_reserve(test_slab_list);
138 TEST_CHECK(test_elements[0] != NULL);
139 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
140 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
141
142 test_elements[1] = test_slab_reserve(test_slab_list);
143 test_elements[2] = test_slab_reserve(test_slab_list);
144 test_elements[3] = test_slab_reserve(test_slab_list);
145 TEST_CHECK(test_elements[3] != NULL);
146 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
147 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
148
149 /* This is more elements than max_elements */
150 test_elements[4] = test_slab_reserve(test_slab_list);
151 TEST_CHECK(test_elements[4] == NULL);
152 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
153 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
154
155 talloc_free(test_slab_list);
156}
157
158/** Test that freeing an element makes it available for reuse with the element reset between uses
159 *
160 */
161static void test_reuse_reset(void)
162{
163 test_slab_list_t *test_slab_list;
164 test_element_t *test_elements[5];
165 test_uctx_t test_uctx;
166
167 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, true, false);
168 TEST_ASSERT(test_slab_list != NULL);
169
170 test_elements[0] = test_slab_reserve(test_slab_list);
171 TEST_CHECK(test_elements[0] != NULL);
172 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
173 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
174
175 test_uctx.count = 0;
176
177 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
178 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
179
180 test_elements[1] = test_slab_reserve(test_slab_list);
181 TEST_CHECK(test_elements[1] != NULL);
182 TEST_CHECK(test_elements[1] != test_elements[0]);
183
184 test_elements[2] = test_slab_reserve(test_slab_list);
185 TEST_CHECK(test_elements[2] != NULL);
186
187 test_elements[3] = test_slab_reserve(test_slab_list);
188 TEST_CHECK(test_elements[3] != NULL);
189
190 if (test_elements[0]) test_slab_release(test_elements[0]);
191 TEST_CHECK(test_uctx.count == 11);
192
193 /*
194 * Having released the first element allocated from a slab
195 * reserving another should grab that first one again, but
196 * with the entry memset to zero.
197 */
198 test_elements[4] = test_slab_reserve(test_slab_list);
199 TEST_CHECK(test_elements[4] != NULL);
200 TEST_CHECK(test_elements[4] == test_elements[0]);
201 if (test_elements[4]) TEST_CHECK(test_elements[4]->name == NULL);
202 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
203 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
204
205 /*
206 * Releasing the first element should reset the destructor
207 * so releasing this reuse of it will not update the result
208 * of the initial release.
209 */
210 if (test_elements[4]) test_elements[4]->name = talloc_strdup(test_elements[4], "Different length string");
211 if (test_elements[4]) test_slab_release(test_elements[4]);
212 TEST_CHECK(test_uctx.count == 11);
213
214 talloc_free(test_slab_list);
215}
216
217/** Test that freeing an element makes it available for reuse with the element not reset between uses
218 *
219 */
220static void test_reuse_noreset(void)
221{
222 test_slab_list_t *test_slab_list;
223 test_element_t *test_elements[3];
224 test_uctx_t test_uctx;
225
226 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, false, false);
227 TEST_ASSERT(test_slab_list != NULL);
228
229 test_elements[0] = test_slab_reserve(test_slab_list);
230 TEST_CHECK(test_elements[0] != NULL);
231 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
232 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
233
234 test_uctx.count = 0;
235
236 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
237 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
238
239 test_elements[1] = test_slab_reserve(test_slab_list);
240 TEST_CHECK(test_elements[1] != NULL);
241 TEST_CHECK(test_elements[1] != test_elements[0]);
242
243 if (test_elements[0]) test_slab_release(test_elements[0]);
244 TEST_CHECK(test_uctx.count == 11);
245
246 /*
247 * Having released the first element allocated from a slab
248 * reserving another should grab that first one again.
249 * Since no reset was done, the element should be as it was before.
250 */
251 test_elements[2] = test_slab_reserve(test_slab_list);
252 TEST_CHECK(test_elements[2] != NULL);
253 TEST_CHECK(test_elements[2] == test_elements[0]);
254 if (test_elements[0] && test_elements[2]) TEST_CHECK(test_elements[2]->name == test_elements[0]->name);
255
256 /*
257 * Replace the element's string so that the callback on release has
258 * a different string to work on.
259 */
260 if (test_elements[2]) talloc_free(test_elements[2]->name);
261 if (test_elements[2]) test_elements[2]->name = talloc_strdup(test_elements[2], "Different length string");
262 if (test_elements[2]) test_slab_release(test_elements[2]);
263 TEST_CHECK(test_uctx.count == 23);
264
265 talloc_free(test_slab_list);
266}
267
268/** Test that setting reserve_mru to true works
269 *
270 * After releasing an element, a subsequent reserve should return the same element
271 */
272static void test_reserve_mru(void)
273{
274 test_slab_list_t *test_slab_list;
275 test_element_t *test_elements[2];
276
277 /*
278 * First use a slab list with reserve_mru = false to verify that the two reservations
279 * result in different elements being returned
280 */
281 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, true, false);
282 TEST_ASSERT(test_slab_list != NULL);
283
284 test_elements[0] = test_slab_reserve(test_slab_list);
285 TEST_CHECK(test_elements[0] != NULL);
286
287 if (test_elements[0]) test_slab_release(test_elements[0]);
288
289 test_elements[1] = test_slab_reserve(test_slab_list);
290 TEST_CHECK(test_elements[1] != NULL);
291 TEST_CHECK(test_elements[0] != test_elements[1]);
292
293 talloc_free(test_slab_list);
294
295 /*
296 * Now use a slab list with reserve_mru = true
297 */
298 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, true, true);
299 TEST_ASSERT(test_slab_list != NULL);
300
301 test_elements[0] = test_slab_reserve(test_slab_list);
302 TEST_CHECK(test_elements[0] != NULL);
303
304 if (test_elements[0]) test_slab_release(test_elements[0]);
305
306 test_elements[1] = test_slab_reserve(test_slab_list);
307 TEST_CHECK(test_elements[1] != NULL);
308 TEST_CHECK(test_elements[0] == test_elements[1]);
309
310 talloc_free(test_slab_list);
311}
312
313/** Test that freeing an element results in the destructor being called,
314 * and that all accounting is correctly updated.
315 */
316static void test_free(void)
317{
318 test_slab_list_t *test_slab_list;
319 test_element_t *test_element;
320 test_uctx_t test_uctx;
321 fr_slab_config_t slab_config = def_slab_config;
322
323 slab_config.allow_direct_free = true;
324 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
325 TEST_ASSERT(test_slab_list != NULL);
326
327 test_element = test_slab_reserve(test_slab_list);
328 TEST_CHECK(test_element != NULL);
329
330 test_uctx.count = 0;
331
332 if (test_element) test_element->name = talloc_strdup(test_element, "Hello there");
333 if (test_element) test_slab_element_set_destructor(test_element, test_element_free, &test_uctx);
334
335 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
336
337 if (test_element) talloc_free(test_element);
338 TEST_CHECK(test_uctx.count == 11);
339 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
340
341 talloc_free(test_slab_list);
342}
343
344static int test_element_alloc(test_element_t *elem, void *uctx)
345{
346 test_conf_t *test_conf = uctx;
347 elem->num = test_conf->initial;
348 return 0;
349}
350
351/** Test that a callback correctly initialises slab elements on first use
352 *
353 */
354static void test_init(void)
355{
356 test_slab_list_t *test_slab_list;
357 test_element_t *test_elements[2];
358 test_conf_t test_conf = { .initial = 10 };
359 fr_slab_config_t slab_config = def_slab_config;
360
361 slab_config.elements_per_slab = 1;
362 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, test_element_alloc, NULL, &test_conf, false, false);
363 TEST_ASSERT(test_slab_list != NULL);
364
365 test_elements[0] = test_slab_reserve(test_slab_list);
366 TEST_CHECK(test_elements[0] != NULL);
367 TEST_CHECK(test_elements[0] && (test_elements[0]->num == 10));
368
369 /*
370 * Change element data and release
371 */
372 if (test_elements[0]) {
373 test_elements[0]->num = 5;
374 test_slab_release(test_elements[0]);
375 }
376
377 /*
378 * Re-reserve and check element is unchanged.
379 */
380 test_elements[1] = test_slab_reserve(test_slab_list);
381 TEST_CHECK(test_elements[1] != NULL);
382 TEST_CHECK(test_elements[1] == test_elements[0]);
383 if (test_elements[1]) TEST_CHECK(test_elements[1]->num == 5);
384
385 talloc_free(test_slab_list);
386}
387
388/** Test that a reserve callback correctly initialises slab elements
389 *
390 */
391static void test_reserve(void)
392{
393 test_slab_list_t *test_slab_list;
394 test_element_t *test_elements[2];
395 test_conf_t test_conf = { .initial = 10 };
396 fr_slab_config_t slab_config = def_slab_config;
397
398 slab_config.elements_per_slab = 1;
399 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, test_element_alloc, &test_conf, false, false);
400 TEST_ASSERT(test_slab_list != NULL);
401
402 test_elements[0] = test_slab_reserve(test_slab_list);
403 TEST_CHECK(test_elements[0] != NULL);
404 TEST_CHECK(test_elements[0] && (test_elements[0]->num == 10));
405
406 /*
407 * Change element data and release
408 */
409 if (test_elements[0]) {
410 test_elements[0]->num = 5;
411 test_slab_release(test_elements[0]);
412 }
413
414 /*
415 * Re-reserve and check element is re-initialised.
416 */
417 test_elements[1] = test_slab_reserve(test_slab_list);
418 TEST_CHECK(test_elements[1] != NULL);
419 TEST_CHECK(test_elements[1] == test_elements[0]);
420 if (test_elements[1]) TEST_CHECK(test_elements[1]->num == 10);
421
422 talloc_free(test_slab_list);
423}
424
425static int test_element_reserve(test_element_t *elem, void *uctx)
426{
427 test_conf_t *test_conf = uctx;
428 elem->num = test_conf->initial * 2;
429 return 0;
430}
431
432/** Test that reserve callback runs after init callback
433 *
434 */
435static void test_init_reserve(void)
436{
437 test_slab_list_t *test_slab_list;
438 test_element_t *test_elements[2];
439 test_conf_t test_conf = { .initial = 10 };
440 fr_slab_config_t slab_config = def_slab_config;
441
442 slab_config.elements_per_slab = 1;
443 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, test_element_alloc, test_element_reserve, &test_conf, false, false);
444 TEST_ASSERT(test_slab_list != NULL);
445
446 test_elements[0] = test_slab_reserve(test_slab_list);
447 TEST_CHECK(test_elements[0] != NULL);
448 TEST_CHECK(test_elements[0] && (test_elements[0]->num == 20));
449
450 /*
451 * Change element data and release
452 */
453 if (test_elements[0]) {
454 test_elements[0]->num = 5;
455 test_slab_release(test_elements[0]);
456 }
457
458 /*
459 * Re-reserve and check reset callback is run.
460 */
461 test_elements[1] = test_slab_reserve(test_slab_list);
462 TEST_CHECK(test_elements[1] != NULL);
463 TEST_CHECK(test_elements[1] == test_elements[0]);
464 if (test_elements[1]) TEST_CHECK(test_elements[1]->num == 20);
465
466 talloc_free(test_slab_list);
467}
468
469/** Test of clearing unused slabs
470 *
471 */
472static void test_clearup_1(void)
473{
474 TALLOC_CTX *ctx = talloc_init_const("test");
476 test_slab_list_t *test_slab_list;
477 test_element_t *test_elements[6];
478 int i, events;
479 fr_slab_config_t slab_config = def_slab_config;
480
481 el = fr_event_list_alloc(ctx, NULL, NULL);
483
484 slab_config.max_elements = 6;
485 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
486 TEST_ASSERT(test_slab_list != NULL);
487
488 /*
489 * Allocate all the slab elements
490 */
491 for (i = 0; i < 6; i++) {
492 test_elements[i] = test_slab_reserve(test_slab_list);
493 TEST_CHECK(test_elements[i] != NULL);
494 }
495 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 3);
496 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 6);
497
498 /*
499 * Release four of the six elements
500 */
501 for (i = 0; i < 4; i++) {
502 test_slab_release(test_elements[i]);
503 }
504 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 3);
505 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
506
507 /*
508 * Running clearup should free one slab - half of the
509 * difference between the high water mark and the in use count.
510 */
513 TEST_CHECK(events == 1);
515 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
516 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
517
518 talloc_free(test_slab_list);
519 talloc_free(ctx);
520}
521
522/** Test that slab clearing does not go beyond the minimum
523 *
524 */
525static void test_clearup_2(void)
526{
527 TALLOC_CTX *ctx = talloc_init_const("test");
529 test_slab_list_t *test_slab_list;
530 test_element_t *test_elements[20];
531 int i, events;
532 fr_slab_config_t slab_config = def_slab_config;
533
534 el = fr_event_list_alloc(ctx, NULL, NULL);
536
537 slab_config.min_elements = 16;
538 slab_config.max_elements = 20;
539 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
540 TEST_ASSERT(test_slab_list != NULL);
541
542 /*
543 * Allocate all the slab elements
544 */
545 for (i = 0; i < 20; i++) {
546 test_elements[i] = test_slab_reserve(test_slab_list);
547 TEST_CHECK(test_elements[i] != NULL);
548 }
549 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
550 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
551
552 /*
553 * Release all of the elements
554 */
555 for (i = 0; i < 20; i++) {
556 test_slab_release(test_elements[i]);
557 }
558 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
559 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
560
561 /*
562 * Running clearup should free two slabs - the minimum element
563 * count will keep the remainder allocated
564 */
567 TEST_CHECK(events == 1);
569 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 8);
570 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
571
572 /*
573 * Re-run the event - no more slabs should be cleared
574 */
577 TEST_CHECK(events == 1);
579 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 8);
580 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
581
582 talloc_free(test_slab_list);
583 talloc_free(ctx);
584}
585
586/** Test that repeated clearing frees more slabs
587 *
588 */
589static void test_clearup_3(void)
590{
591 TALLOC_CTX *ctx = talloc_init_const("test");
593 test_slab_list_t *test_slab_list;
594 test_element_t *test_elements[20];
595 int i, events;
596 fr_slab_config_t slab_config = def_slab_config;
597
598 el = fr_event_list_alloc(ctx, NULL, NULL);
600
601 slab_config.min_elements = 0;
602 slab_config.max_elements = 20;
603 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
604 TEST_ASSERT(test_slab_list != NULL);
605
606 /*
607 * Allocate all the slab elements
608 */
609 for (i = 0; i < 20; i++) {
610 test_elements[i] = test_slab_reserve(test_slab_list);
611 TEST_CHECK(test_elements[i] != NULL);
612 }
613 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
614 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
615
616 /*
617 * Release all of the elements
618 */
619 for (i = 0; i < 20; i++) {
620 test_slab_release(test_elements[i]);
621 }
622 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
623 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
624
625 /*
626 * Running clearup should free five slabs (20 - 0) / 2 / 2 = 5
627 */
630 TEST_CHECK(events == 1);
632 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 5);
633 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
634
635 /*
636 * Re-run the event - two more slabs should be freed (10 - 0) / 2 / 2 = 2.5
637 */
640 TEST_CHECK(events == 1);
642 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 3);
643 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
644
645 /*
646 * Re-run the event - one more slab should be freed (6 - 0) / 2 / 2 = 1.5
647 */
650 TEST_CHECK(events == 1);
652 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
653 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
654
655 /*
656 * Re-run the event - one more slab should be freed (4 - 0) / 2 / 2 = 1
657 */
660 TEST_CHECK(events == 1);
662 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
663 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
664
665 /*
666 * Re-run the event - no more will be freed as (2 - 0) / 2 / 2 = 0.5
667 */
670 TEST_CHECK(events == 1);
672 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
673 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
674
675 talloc_free(test_slab_list);
676 talloc_free(ctx);
677}
678
679/** Test that reserving after clearup results in new slab allocation
680 *
681 */
682static void test_realloc(void)
683{
684 TALLOC_CTX *ctx = talloc_init_const("test");
686 test_slab_list_t *test_slab_list;
687 test_element_t *test_elements[20];
688 int i, events;
689 fr_slab_config_t slab_config = def_slab_config;
690
691 el = fr_event_list_alloc(ctx, NULL, NULL);
693
694 slab_config.min_elements = 0;
695 slab_config.max_elements = 20;
696 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
697 TEST_ASSERT(test_slab_list != NULL);
698
699 /*
700 * Allocate all the slab elements
701 */
702 for (i = 0; i < 20; i++) {
703 test_elements[i] = test_slab_reserve(test_slab_list);
704 TEST_CHECK(test_elements[i] != NULL);
705 }
706 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
707 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
708
709 /*
710 * Release all of the elements
711 */
712 for (i = 0; i < 20; i++) {
713 test_slab_release(test_elements[i]);
714 }
715 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
716 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
717
718 /*
719 * Running clearup should free five slabs
720 */
723 TEST_CHECK(events == 1);
725 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 5);
726 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
727
728 /*
729 * Allocate all the slab elements
730 * With new slabs allocated, the slab stats will change
731 */
732 for (i = 0; i < 20; i++) {
733 test_elements[i] = test_slab_reserve(test_slab_list);
734 TEST_CHECK(test_elements[i] != NULL);
735 }
736 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
737 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
738
739 talloc_free(test_slab_list);
740 talloc_free(ctx);
741}
742
743/** Test that talloc freeing multiple in-use elements correctly updates accounting
744 */
745static void test_free_multiple(void)
746{
747 test_slab_list_t *test_slab_list;
748 test_element_t *test_elements[4];
749 test_uctx_t test_uctx;
750 fr_slab_config_t slab_config = def_slab_config;
751 int i;
752
753 slab_config.allow_direct_free = true;
754 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
755 TEST_ASSERT(test_slab_list != NULL);
756
757 for (i = 0; i < 4; i++) {
758 test_elements[i] = test_slab_reserve(test_slab_list);
759 TEST_CHECK(test_elements[i] != NULL);
760 }
761 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
762
763 test_uctx.count = 0;
764 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "first");
765 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
766
767 /*
768 * Free elements one at a time, checking accounting after each
769 */
770 if (test_elements[0]) talloc_free(test_elements[0]);
771 TEST_CHECK(test_uctx.count == 5);
772 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 3);
773
774 if (test_elements[1]) talloc_free(test_elements[1]);
775 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
776
777 if (test_elements[2]) talloc_free(test_elements[2]);
778 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
779
780 if (test_elements[3]) talloc_free(test_elements[3]);
781 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
782
783 talloc_free(test_slab_list);
784}
785
786/** Test that talloc free of the slab list with in-use elements does not assert.
787 *
788 * This exercises the being_freed flag path: the slab destructor sets
789 * being_freed = true before children are freed, so element destructors
790 * will see the flag and skip the direct-free assertion.
791 */
792static void test_bulk_teardown(void)
793{
794 test_slab_list_t *test_slab_list;
795 test_element_t *test_elements[4];
796 test_uctx_t test_uctx;
797 int i;
798
799 /*
800 * Deliberately do NOT set allow_direct_free.
801 * The being_freed flag should suppress the assertion.
802 */
803 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, true, false);
804 TEST_ASSERT(test_slab_list != NULL);
805
806 for (i = 0; i < 4; i++) {
807 test_elements[i] = test_slab_reserve(test_slab_list);
808 TEST_CHECK(test_elements[i] != NULL);
809 }
810 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
811
812 test_uctx.count = 0;
813 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "bulk teardown");
814 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
815
816 /*
817 * Free the entire slab list with elements still in use.
818 * This should NOT assert because the slab destructor sets
819 * being_freed = true before children are freed.
820 */
821 talloc_free(test_slab_list);
822
823 /*
824 * The element destructor should have been called during teardown.
825 */
826 TEST_CHECK(test_uctx.count == 13);
827}
828
829/** Test mixed release and talloc_free of elements from the same slab
830 */
831static void test_free_mixed(void)
832{
833 test_slab_list_t *test_slab_list;
834 test_element_t *test_elements[4];
835 test_uctx_t test_uctx1, test_uctx2;
836 fr_slab_config_t slab_config = def_slab_config;
837
838 slab_config.allow_direct_free = true;
839 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
840 TEST_ASSERT(test_slab_list != NULL);
841
842 test_elements[0] = test_slab_reserve(test_slab_list);
843 test_elements[1] = test_slab_reserve(test_slab_list);
844 test_elements[2] = test_slab_reserve(test_slab_list);
845 test_elements[3] = test_slab_reserve(test_slab_list);
846 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
847
848 test_uctx1.count = 0;
849 test_uctx2.count = 0;
850 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "release me");
851 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx1);
852 if (test_elements[1]) test_elements[1]->name = talloc_strdup(test_elements[1], "free me");
853 if (test_elements[1]) test_slab_element_set_destructor(test_elements[1], test_element_free, &test_uctx2);
854
855 /*
856 * Release element 0 via slab_release, free element 1 via talloc_free
857 */
858 if (test_elements[0]) test_slab_release(test_elements[0]);
859 TEST_CHECK(test_uctx1.count == 10);
860 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 3);
861
862 if (test_elements[1]) talloc_free(test_elements[1]);
863 TEST_CHECK(test_uctx2.count == 7);
864 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
865
866 /*
867 * Release the remaining two normally
868 */
869 if (test_elements[2]) test_slab_release(test_elements[2]);
870 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
871
872 if (test_elements[3]) test_slab_release(test_elements[3]);
873 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
874
875 talloc_free(test_slab_list);
876}
877
878/** Test that reserving after talloc_free of an element still works
879 */
880static void test_free_and_reserve(void)
881{
882 test_slab_list_t *test_slab_list;
883 test_element_t *test_elements[3];
884 fr_slab_config_t slab_config = def_slab_config;
885
886 slab_config.allow_direct_free = true;
887 slab_config.at_max_fail = true;
888 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
889 TEST_ASSERT(test_slab_list != NULL);
890
891 /*
892 * Reserve all four slots
893 */
894 test_elements[0] = test_slab_reserve(test_slab_list);
895 test_elements[1] = test_slab_reserve(test_slab_list);
896 TEST_CHECK(test_elements[0] != NULL);
897 TEST_CHECK(test_elements[1] != NULL);
898 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
899
900 /*
901 * talloc_free an in-use element. This permanently removes
902 * it from the slab (it does NOT return to the available list),
903 * so the slab loses capacity.
904 */
905 if (test_elements[0]) talloc_free(test_elements[0]);
906 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
907
908 /*
909 * We should still be able to reserve new elements from the
910 * remaining available slots.
911 */
912 test_elements[2] = test_slab_reserve(test_slab_list);
913 TEST_CHECK(test_elements[2] != NULL);
914 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
915
916 talloc_free(test_slab_list);
917}
918
919static void test_child_alloc(void)
920{
921 test_slab_list_t *test_slab_list;
922 test_element_t *test_elements[2];
923 fr_slab_config_t slab_config = def_slab_config;
924
925 slab_config.max_elements = 2;
926 slab_config.num_children = 1;
927 slab_config.child_pool_size = 128;
928 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, false, false);
929 TEST_ASSERT(test_slab_list != NULL);
930
931 test_elements[0] = test_slab_reserve(test_slab_list);
932 TEST_CHECK(test_elements[0] != NULL);
933 test_elements[1] = test_slab_reserve(test_slab_list);
934 TEST_CHECK(test_elements[1] != NULL);
935
936 /*
937 * Allocate a child of the first element. If this has used the pool memory
938 * allocated to the element, then it's location should be after the element
939 * but before the next element.
940 * This is a rough test, which can be improved if additional talloc functions
941 * become available to check pool allocation status.
942 */
943 if (test_elements[0]) {
944 test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
945 TEST_CHECK((void *)test_elements[0]->name > (void *)test_elements[0]);
946 if (test_elements[1] && (test_elements[1] > test_elements[0])) {
947 TEST_CHECK((void *)test_elements[0]->name < (void *)test_elements[1]);
948 TEST_MSG("element 0: %p, name %p, element 1: %p",
949 test_elements[0], test_elements[0]->name, test_elements[1]);
950 }
951 }
952
953 talloc_free(test_slab_list);
954}
955
957 { "test_alloc", test_alloc },
958 { "test_alloc_fail", test_alloc_fail },
959 { "test_reuse_reset", test_reuse_reset },
960 { "test_reuse_noreset", test_reuse_noreset },
961 { "test_reserve_mru", test_reserve_mru },
962 { "test_free", test_free },
963 { "test_free_multiple", test_free_multiple },
964 { "test_bulk_teardown", test_bulk_teardown },
965 { "test_free_mixed", test_free_mixed },
966 { "test_free_and_reserve", test_free_and_reserve },
967 { "test_init", test_init },
968 { "test_reserve", test_reserve },
969 { "test_init_reserve", test_init_reserve },
970 { "test_clearup_1", test_clearup_1 },
971 { "test_clearup_2", test_clearup_2 },
972 { "test_clearup_3", test_clearup_3 },
973 { "test_realloc", test_realloc },
974 { "test_child_alloc", test_child_alloc },
975
977};
#define TEST_CHECK(cond)
Definition acutest.h:87
#define TEST_ASSERT(cond)
Definition acutest.h:110
#define TEST_TERMINATOR
Definition acutest.h:64
#define TEST_MSG(...)
Definition acutest.h:217
#define TEST_CHECK_RET(_got, _exp)
talloc_free(hp)
void fr_event_service(fr_event_list_t *el)
Service any outstanding timer or file descriptor events.
Definition event.c:2177
int fr_event_corral(fr_event_list_t *el, fr_time_t now, bool wait)
Gather outstanding timer and file descriptor events.
Definition event.c:2045
fr_event_list_t * fr_event_list_alloc(TALLOC_CTX *ctx, fr_event_status_cb_t status, void *status_uctx)
Initialise a new event list.
Definition event.c:2506
Stores all information relating to an event list.
Definition event.c:377
static fr_event_list_t * events
Definition radsniff.c:59
static char const * name
#define FR_SLAB_FUNCS(_name, _type)
Define type specific wrapper functions for slabs and slab elements.
Definition slab.h:124
#define FR_SLAB_TYPES(_name, _type)
Define type specific wrapper structs for slabs and slab elements.
Definition slab.h:75
unsigned int min_elements
Minimum number of elements to keep allocated.
Definition slab.h:44
unsigned int max_elements
Maximum number of elements to allocate using slabs.
Definition slab.h:45
bool at_max_fail
Should requests for additional elements fail when the number in use has reached max_elements.
Definition slab.h:46
unsigned int elements_per_slab
Number of elements to allocate per slab.
Definition slab.h:43
unsigned int num_children
How many child allocations are expected off each element.
Definition slab.h:48
size_t child_pool_size
Size of pool space to be allocated to each element.
Definition slab.h:49
bool allow_direct_free
Allow in-use elements to be freed with talloc_free.
Definition slab.h:50
Tuneable parameters for slabs.
Definition slab.h:42
static fr_time_t test_time(void)
Definition slab_tests.c:43
static void test_clearup_3(void)
Test that repeated clearing frees more slabs.
Definition slab_tests.c:589
static void test_reserve(void)
Test that a reserve callback correctly initialises slab elements.
Definition slab_tests.c:391
TEST_LIST
Definition slab_tests.c:956
static fr_time_t test_time_base
Definition slab_tests.c:42
static void test_init_reserve(void)
Test that reserve callback runs after init callback.
Definition slab_tests.c:435
static fr_slab_config_t def_slab_config
Definition slab_tests.c:25
static void test_reserve_mru(void)
Test that setting reserve_mru to true works.
Definition slab_tests.c:272
static void test_clearup_2(void)
Test that slab clearing does not go beyond the minimum.
Definition slab_tests.c:525
static void test_reuse_noreset(void)
Test that freeing an element makes it available for reuse with the element not reset between uses.
Definition slab_tests.c:220
static void test_child_alloc(void)
Definition slab_tests.c:919
static int test_element_reserve(test_element_t *elem, void *uctx)
Definition slab_tests.c:425
static void test_clearup_1(void)
Test of clearing unused slabs.
Definition slab_tests.c:472
static void test_free(void)
Test that freeing an element results in the destructor being called, and that all accounting is corre...
Definition slab_tests.c:316
static void test_alloc(void)
Test basic allocation and reservation of elements.
Definition slab_tests.c:54
static void test_reuse_reset(void)
Test that freeing an element makes it available for reuse with the element reset between uses.
Definition slab_tests.c:161
static int test_element_free(test_element_t *elem, void *uctx)
Definition slab_tests.c:35
static void test_free_and_reserve(void)
Test that reserving after talloc_free of an element still works.
Definition slab_tests.c:880
static void test_free_mixed(void)
Test mixed release and talloc_free of elements from the same slab.
Definition slab_tests.c:831
static void test_init(void)
Test that a callback correctly initialises slab elements on first use.
Definition slab_tests.c:354
static void test_bulk_teardown(void)
Test that talloc free of the slab list with in-use elements does not assert.
Definition slab_tests.c:792
static void test_alloc_fail(void)
Test allocation beyond max fails correctly.
Definition slab_tests.c:124
static int test_element_alloc(test_element_t *elem, void *uctx)
Definition slab_tests.c:344
static void test_realloc(void)
Test that reserving after clearup results in new slab allocation.
Definition slab_tests.c:682
static void test_free_multiple(void)
Test that talloc freeing multiple in-use elements correctly updates accounting.
Definition slab_tests.c:745
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition talloc.h:123
#define talloc_strdup(_ctx, _str)
Definition talloc.h:145
static fr_time_t fr_time_add_time_delta(fr_time_t a, fr_time_delta_t b)
Definition time.h:173
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_wrap(_time)
Definition time.h:145
#define NSEC
Definition time.h:379
"server local" time.
Definition time.h:69
void fr_timer_list_set_time_func(fr_timer_list_t *tl, fr_event_time_source_t func)
Override event list time source.
Definition timer.c:1200
static fr_event_list_t * el