The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
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 */
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/** How many times each callback has run
433 */
434typedef struct {
435 int alloc; //!< Calls to the alloc callback.
436 int reserve; //!< Calls to the reserve callback.
438
439static int test_element_count_alloc(test_element_t *elem, void *uctx)
440{
441 test_count_t *count = uctx;
442
443 count->alloc++;
444
445 /*
446 * Record which initialisation this element got, so a second one
447 * can be seen in the element itself and not just in the counter.
448 */
449 elem->num = count->alloc;
450 return 0;
451}
452
454{
455 test_count_t *count = uctx;
456
457 count->reserve++;
458 return 0;
459}
460
461/** Test that reserve callback runs after init callback
462 *
463 */
464static void test_init_reserve(void)
465{
466 test_slab_list_t *test_slab_list;
467 test_element_t *test_elements[2];
468 test_conf_t test_conf = { .initial = 10 };
469 fr_slab_config_t slab_config = def_slab_config;
470
471 slab_config.elements_per_slab = 1;
472 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, test_element_alloc, test_element_reserve, &test_conf, false, false);
473 TEST_ASSERT(test_slab_list != NULL);
474
475 test_elements[0] = test_slab_reserve(test_slab_list);
476 TEST_CHECK(test_elements[0] != NULL);
477 TEST_CHECK(test_elements[0] && (test_elements[0]->num == 20));
478
479 /*
480 * Change element data and release
481 */
482 if (test_elements[0]) {
483 test_elements[0]->num = 5;
484 test_slab_release(test_elements[0]);
485 }
486
487 /*
488 * Re-reserve and check reset callback is run.
489 */
490 test_elements[1] = test_slab_reserve(test_slab_list);
491 TEST_CHECK(test_elements[1] != NULL);
492 TEST_CHECK(test_elements[1] == test_elements[0]);
493 if (test_elements[1]) TEST_CHECK(test_elements[1]->num == 20);
494
495 talloc_free(test_slab_list);
496}
497
498/** Count the callbacks across a reserve, release, reserve cycle
499 *
500 * Releasing an element hands it back to the slab, and reserving again hands
501 * back the same element with whatever the alloc callback set up still in place.
502 * Callers rely on that. rlm_ftp, rlm_imap, rlm_rest and rlm_smtp create a CURL
503 * handle in their alloc callback, and rlm_krb5 creates a Kerberos context.
504 * None of those should be built again for every request.
505 *
506 * So the alloc callback runs once per element, and the reserve callback runs
507 * once per reserve. Nothing else in this file measures the difference: a
508 * change which turned the slab into a plain memory recycler, freeing each
509 * element on release and building a new one on the next reserve, would still
510 * pass every other test here. The counts below are what notices.
511 */
513{
514 test_slab_list_t *test_slab_list;
515 test_element_t *element, *first;
516 test_count_t count = {};
517 fr_slab_config_t slab_config = def_slab_config;
518 unsigned int i;
519
520 /*
521 * One element per slab and one element allowed, so every reserve
522 * after the first has to hand back the element already made.
523 */
524 slab_config.elements_per_slab = 1;
525 slab_config.min_elements = 1;
526 slab_config.max_elements = 1;
527
528 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config,
530 &count, false, false);
531 TEST_ASSERT(test_slab_list != NULL);
532
533 TEST_CASE("Neither callback runs before the first reserve");
534 TEST_CHECK_RET(count.alloc, 0);
535 TEST_CHECK_RET(count.reserve, 0);
536
537 TEST_CASE("The first reserve makes one element and initialises it once");
538 first = test_slab_reserve(test_slab_list);
539 TEST_ASSERT(first != NULL);
540 TEST_CHECK_RET(count.alloc, 1);
541 TEST_CHECK_RET(count.reserve, 1);
542 TEST_CHECK_RET(first->num, 1);
543
544 TEST_CASE("Reserve, release, reserve does not initialise the element again");
545 element = first;
546 for (i = 0; i < 5; i++) {
547 test_slab_release(element);
548
549 element = test_slab_reserve(test_slab_list);
550 TEST_ASSERT(element != NULL);
551 TEST_CHECK(element == first);
552 TEST_MSG("cycle %u was handed a different element", i);
553 }
554
555 TEST_CHECK_RET(count.alloc, 1);
556 TEST_MSG("the alloc callback ran once per reserve rather than once per "
557 "element, so released elements are being rebuilt instead of reused");
558
559 TEST_CHECK_RET(count.reserve, 6);
560
561 /*
562 * num still holds what the alloc callback wrote the one time it ran.
563 */
564 TEST_CHECK_RET(element->num, 1);
565
566 talloc_free(test_slab_list);
567}
568
569/** Test of clearing unused slabs
570 *
571 */
572static void test_clearup_1(void)
573{
574 TALLOC_CTX *ctx = talloc_init_const("test");
576 test_slab_list_t *test_slab_list;
577 test_element_t *test_elements[6];
578 int i, events;
579 fr_slab_config_t slab_config = def_slab_config;
580
581 el = fr_event_list_alloc(ctx, NULL, NULL);
583
584 slab_config.max_elements = 6;
585 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
586 TEST_ASSERT(test_slab_list != NULL);
587
588 /*
589 * Allocate all the slab elements
590 */
591 for (i = 0; i < 6; i++) {
592 test_elements[i] = test_slab_reserve(test_slab_list);
593 TEST_CHECK(test_elements[i] != NULL);
594 }
595 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 3);
596 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 6);
597
598 /*
599 * Release four of the six elements
600 */
601 for (i = 0; i < 4; i++) {
602 test_slab_release(test_elements[i]);
603 }
604 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 3);
605 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
606
607 /*
608 * Running clearup should free one slab - half of the
609 * difference between the high water mark and the in use count.
610 */
613 TEST_CHECK(events == 1);
615 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
616 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
617
618 talloc_free(test_slab_list);
619 talloc_free(ctx);
620}
621
622/** Test that slab clearing does not go beyond the minimum
623 *
624 */
625static void test_clearup_2(void)
626{
627 TALLOC_CTX *ctx = talloc_init_const("test");
629 test_slab_list_t *test_slab_list;
630 test_element_t *test_elements[20];
631 int i, events;
632 fr_slab_config_t slab_config = def_slab_config;
633
634 el = fr_event_list_alloc(ctx, NULL, NULL);
636
637 slab_config.min_elements = 16;
638 slab_config.max_elements = 20;
639 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
640 TEST_ASSERT(test_slab_list != NULL);
641
642 /*
643 * Allocate all the slab elements
644 */
645 for (i = 0; i < 20; i++) {
646 test_elements[i] = test_slab_reserve(test_slab_list);
647 TEST_CHECK(test_elements[i] != NULL);
648 }
649 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
650 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
651
652 /*
653 * Release all of the elements
654 */
655 for (i = 0; i < 20; i++) {
656 test_slab_release(test_elements[i]);
657 }
658 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
659 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
660
661 /*
662 * Running clearup should free two slabs - the minimum element
663 * count will keep the remainder allocated
664 */
667 TEST_CHECK(events == 1);
669 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 8);
670 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
671
672 /*
673 * Re-run the event - no more slabs should be cleared
674 */
677 TEST_CHECK(events == 1);
679 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 8);
680 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
681
682 talloc_free(test_slab_list);
683 talloc_free(ctx);
684}
685
686/** Test that repeated clearing frees more slabs
687 *
688 */
689static void test_clearup_3(void)
690{
691 TALLOC_CTX *ctx = talloc_init_const("test");
693 test_slab_list_t *test_slab_list;
694 test_element_t *test_elements[20];
695 int i, events;
696 fr_slab_config_t slab_config = def_slab_config;
697
698 el = fr_event_list_alloc(ctx, NULL, NULL);
700
701 slab_config.min_elements = 0;
702 slab_config.max_elements = 20;
703 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
704 TEST_ASSERT(test_slab_list != NULL);
705
706 /*
707 * Allocate all the slab elements
708 */
709 for (i = 0; i < 20; i++) {
710 test_elements[i] = test_slab_reserve(test_slab_list);
711 TEST_CHECK(test_elements[i] != NULL);
712 }
713 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
714 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
715
716 /*
717 * Release all of the elements
718 */
719 for (i = 0; i < 20; i++) {
720 test_slab_release(test_elements[i]);
721 }
722 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
723 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
724
725 /*
726 * Running clearup should free five slabs (20 - 0) / 2 / 2 = 5
727 */
730 TEST_CHECK(events == 1);
732 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 5);
733 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
734
735 /*
736 * Re-run the event - two more slabs should be freed (10 - 0) / 2 / 2 = 2.5
737 */
740 TEST_CHECK(events == 1);
742 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 3);
743 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
744
745 /*
746 * Re-run the event - one more slab should be freed (6 - 0) / 2 / 2 = 1.5
747 */
750 TEST_CHECK(events == 1);
752 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 2);
753 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
754
755 /*
756 * Re-run the event - one more slab should be freed (4 - 0) / 2 / 2 = 1
757 */
760 TEST_CHECK(events == 1);
762 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
763 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
764
765 /*
766 * Re-run the event - no more will be freed as (2 - 0) / 2 / 2 = 0.5
767 */
770 TEST_CHECK(events == 1);
772 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 1);
773 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
774
775 talloc_free(test_slab_list);
776 talloc_free(ctx);
777}
778
779/** Test that reserving after clearup results in new slab allocation
780 *
781 */
782static void test_realloc(void)
783{
784 TALLOC_CTX *ctx = talloc_init_const("test");
786 test_slab_list_t *test_slab_list;
787 test_element_t *test_elements[20];
788 int i, events;
789 fr_slab_config_t slab_config = def_slab_config;
790
791 el = fr_event_list_alloc(ctx, NULL, NULL);
793
794 slab_config.min_elements = 0;
795 slab_config.max_elements = 20;
796 test_slab_list = test_slab_list_alloc(NULL, el, &slab_config, NULL, NULL, NULL, true, false);
797 TEST_ASSERT(test_slab_list != NULL);
798
799 /*
800 * Allocate all the slab elements
801 */
802 for (i = 0; i < 20; i++) {
803 test_elements[i] = test_slab_reserve(test_slab_list);
804 TEST_CHECK(test_elements[i] != NULL);
805 }
806 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
807 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
808
809 /*
810 * Release all of the elements
811 */
812 for (i = 0; i < 20; i++) {
813 test_slab_release(test_elements[i]);
814 }
815 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
816 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
817
818 /*
819 * Running clearup should free five slabs
820 */
823 TEST_CHECK(events == 1);
825 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 5);
826 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
827
828 /*
829 * Allocate all the slab elements
830 * With new slabs allocated, the slab stats will change
831 */
832 for (i = 0; i < 20; i++) {
833 test_elements[i] = test_slab_reserve(test_slab_list);
834 TEST_CHECK(test_elements[i] != NULL);
835 }
836 TEST_CHECK_RET(test_slab_num_allocated(test_slab_list), 10);
837 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 20);
838
839 talloc_free(test_slab_list);
840 talloc_free(ctx);
841}
842
843/** Test that talloc freeing multiple in-use elements correctly updates accounting
844 */
845static void test_free_multiple(void)
846{
847 test_slab_list_t *test_slab_list;
848 test_element_t *test_elements[4];
849 test_uctx_t test_uctx;
850 fr_slab_config_t slab_config = def_slab_config;
851 int i;
852
853 slab_config.allow_direct_free = true;
854 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
855 TEST_ASSERT(test_slab_list != NULL);
856
857 for (i = 0; i < 4; i++) {
858 test_elements[i] = test_slab_reserve(test_slab_list);
859 TEST_CHECK(test_elements[i] != NULL);
860 }
861 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
862
863 test_uctx.count = 0;
864 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "first");
865 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
866
867 /*
868 * Free elements one at a time, checking accounting after each
869 */
870 if (test_elements[0]) talloc_free(test_elements[0]);
871 TEST_CHECK(test_uctx.count == 5);
872 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 3);
873
874 if (test_elements[1]) talloc_free(test_elements[1]);
875 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
876
877 if (test_elements[2]) talloc_free(test_elements[2]);
878 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
879
880 if (test_elements[3]) talloc_free(test_elements[3]);
881 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
882
883 talloc_free(test_slab_list);
884}
885
886/** Test that talloc free of the slab list with in-use elements does not assert.
887 *
888 * This exercises the being_freed flag path: the slab destructor sets
889 * being_freed = true before children are freed, so element destructors
890 * will see the flag and skip the direct-free assertion.
891 */
892static void test_bulk_teardown(void)
893{
894 test_slab_list_t *test_slab_list;
895 test_element_t *test_elements[4];
896 test_uctx_t test_uctx;
897 int i;
898
899 /*
900 * Deliberately do NOT set allow_direct_free.
901 * The being_freed flag should suppress the assertion.
902 */
903 test_slab_list = test_slab_list_alloc(NULL, NULL, &def_slab_config, NULL, NULL, NULL, true, false);
904 TEST_ASSERT(test_slab_list != NULL);
905
906 for (i = 0; i < 4; i++) {
907 test_elements[i] = test_slab_reserve(test_slab_list);
908 TEST_CHECK(test_elements[i] != NULL);
909 }
910 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
911
912 test_uctx.count = 0;
913 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "bulk teardown");
914 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx);
915
916 /*
917 * Free the entire slab list with elements still in use.
918 * This should NOT assert because the slab destructor sets
919 * being_freed = true before children are freed.
920 */
921 talloc_free(test_slab_list);
922
923 /*
924 * The element destructor should have been called during teardown.
925 */
926 TEST_CHECK(test_uctx.count == 13);
927}
928
929/** Test mixed release and talloc_free of elements from the same slab
930 */
931static void test_free_mixed(void)
932{
933 test_slab_list_t *test_slab_list;
934 test_element_t *test_elements[4];
935 test_uctx_t test_uctx1, test_uctx2;
936 fr_slab_config_t slab_config = def_slab_config;
937
938 slab_config.allow_direct_free = true;
939 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
940 TEST_ASSERT(test_slab_list != NULL);
941
942 test_elements[0] = test_slab_reserve(test_slab_list);
943 test_elements[1] = test_slab_reserve(test_slab_list);
944 test_elements[2] = test_slab_reserve(test_slab_list);
945 test_elements[3] = test_slab_reserve(test_slab_list);
946 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 4);
947
948 test_uctx1.count = 0;
949 test_uctx2.count = 0;
950 if (test_elements[0]) test_elements[0]->name = talloc_strdup(test_elements[0], "release me");
951 if (test_elements[0]) test_slab_element_set_destructor(test_elements[0], test_element_free, &test_uctx1);
952 if (test_elements[1]) test_elements[1]->name = talloc_strdup(test_elements[1], "free me");
953 if (test_elements[1]) test_slab_element_set_destructor(test_elements[1], test_element_free, &test_uctx2);
954
955 /*
956 * Release element 0 via slab_release, free element 1 via talloc_free
957 */
958 if (test_elements[0]) test_slab_release(test_elements[0]);
959 TEST_CHECK(test_uctx1.count == 10);
960 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 3);
961
962 if (test_elements[1]) talloc_free(test_elements[1]);
963 TEST_CHECK(test_uctx2.count == 7);
964 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
965
966 /*
967 * Release the remaining two normally
968 */
969 if (test_elements[2]) test_slab_release(test_elements[2]);
970 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
971
972 if (test_elements[3]) test_slab_release(test_elements[3]);
973 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 0);
974
975 talloc_free(test_slab_list);
976}
977
978/** Test that reserving after talloc_free of an element still works
979 */
980static void test_free_and_reserve(void)
981{
982 test_slab_list_t *test_slab_list;
983 test_element_t *test_elements[3];
984 fr_slab_config_t slab_config = def_slab_config;
985
986 slab_config.allow_direct_free = true;
987 slab_config.at_max_fail = true;
988 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, true, false);
989 TEST_ASSERT(test_slab_list != NULL);
990
991 /*
992 * Reserve all four slots
993 */
994 test_elements[0] = test_slab_reserve(test_slab_list);
995 test_elements[1] = test_slab_reserve(test_slab_list);
996 TEST_CHECK(test_elements[0] != NULL);
997 TEST_CHECK(test_elements[1] != NULL);
998 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
999
1000 /*
1001 * talloc_free an in-use element. This permanently removes
1002 * it from the slab (it does NOT return to the available list),
1003 * so the slab loses capacity.
1004 */
1005 if (test_elements[0]) talloc_free(test_elements[0]);
1006 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 1);
1007
1008 /*
1009 * We should still be able to reserve new elements from the
1010 * remaining available slots.
1011 */
1012 test_elements[2] = test_slab_reserve(test_slab_list);
1013 TEST_CHECK(test_elements[2] != NULL);
1014 TEST_CHECK_RET(test_slab_num_elements_used(test_slab_list), 2);
1015
1016 talloc_free(test_slab_list);
1017}
1018
1019static void test_child_alloc(void)
1020{
1021 test_slab_list_t *test_slab_list;
1022 test_element_t *test_elements[2];
1023 fr_slab_config_t slab_config = def_slab_config;
1024
1025 slab_config.max_elements = 2;
1026 slab_config.num_children = 1;
1027 slab_config.child_pool_size = 128;
1028 test_slab_list = test_slab_list_alloc(NULL, NULL, &slab_config, NULL, NULL, NULL, false, false);
1029 TEST_ASSERT(test_slab_list != NULL);
1030
1031 test_elements[0] = test_slab_reserve(test_slab_list);
1032 TEST_CHECK(test_elements[0] != NULL);
1033 test_elements[1] = test_slab_reserve(test_slab_list);
1034 TEST_CHECK(test_elements[1] != NULL);
1035
1036 /*
1037 * Allocate a child of the first element. If this has used the pool memory
1038 * allocated to the element, then it's location should be after the element
1039 * but before the next element.
1040 * This is a rough test, which can be improved if additional talloc functions
1041 * become available to check pool allocation status.
1042 */
1043 if (test_elements[0]) {
1044 test_elements[0]->name = talloc_strdup(test_elements[0], "Hello there");
1045 TEST_CHECK((void *)test_elements[0]->name > (void *)test_elements[0]);
1046 if (test_elements[1] && (test_elements[1] > test_elements[0])) {
1047 TEST_CHECK((void *)test_elements[0]->name < (void *)test_elements[1]);
1048 TEST_MSG("element 0: %p, name %p, element 1: %p",
1049 test_elements[0], test_elements[0]->name, test_elements[1]);
1050 }
1051 }
1052
1053 talloc_free(test_slab_list);
1054}
1055
1057 { "test_alloc", test_alloc },
1058 { "test_alloc_fail", test_alloc_fail },
1059 { "test_reuse_reset", test_reuse_reset },
1060 { "test_reuse_noreset", test_reuse_noreset },
1061 { "test_reserve_mru", test_reserve_mru },
1062 { "test_free", test_free },
1063 { "test_free_multiple", test_free_multiple },
1064 { "test_bulk_teardown", test_bulk_teardown },
1065 { "test_free_mixed", test_free_mixed },
1066 { "test_free_and_reserve", test_free_and_reserve },
1067 { "test_init", test_init },
1068 { "test_reserve", test_reserve },
1069 { "test_init_reserve", test_init_reserve },
1070 { "test_alloc_reserve_count", test_alloc_reserve_count },
1071 { "test_clearup_1", test_clearup_1 },
1072 { "test_clearup_2", test_clearup_2 },
1073 { "test_clearup_3", test_clearup_3 },
1074 { "test_realloc", test_realloc },
1075 { "test_child_alloc", test_child_alloc },
1076
1078};
#define TEST_CHECK(cond)
Definition acutest.h:87
#define TEST_CASE(name)
Definition acutest.h:186
#define TEST_ASSERT(cond)
Definition acutest.h:110
#define TEST_TERMINATOR
Definition acutest.h:64
#define TEST_MSG(...)
Definition acutest.h:217
Common initialisation for acutest binaries.
#define TEST_CHECK_RET(_got, _exp)
#define UNUSED
Definition build.h:384
talloc_free(hp)
void fr_event_service(fr_event_list_t *el)
Service any outstanding timer or file descriptor events.
Definition event.c:2205
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:2073
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:2534
Stores all information relating to an event list.
Definition event.c:377
static fr_event_list_t * events
Definition radsniff.c:58
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:689
int alloc
Calls to the alloc callback.
Definition slab_tests.c:435
static void test_reserve(void)
Test that a reserve callback correctly initialises slab elements.
Definition slab_tests.c:391
TEST_LIST
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:464
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 int test_element_count_alloc(test_element_t *elem, void *uctx)
Definition slab_tests.c:439
static void test_clearup_2(void)
Test that slab clearing does not go beyond the minimum.
Definition slab_tests.c:625
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)
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:572
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:980
static void test_free_mixed(void)
Test mixed release and talloc_free of elements from the same slab.
Definition slab_tests.c:931
int reserve
Calls to the reserve callback.
Definition slab_tests.c:436
static void test_alloc_reserve_count(void)
Count the callbacks across a reserve, release, reserve cycle.
Definition slab_tests.c:512
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:892
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:782
static void test_free_multiple(void)
Test that talloc freeing multiple in-use elements correctly updates accounting.
Definition slab_tests.c:845
static int test_element_count_reserve(UNUSED test_element_t *elem, void *uctx)
Definition slab_tests.c:453
How many times each callback has run.
Definition slab_tests.c:434
Definition stest.c:48
static TALLOC_CTX * talloc_init_const(char const *name)
Allocate a top level chunk with a constant name.
Definition talloc.h:127
#define talloc_strdup(_ctx, _str)
Definition talloc.h:149
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:1205
static fr_event_list_t * el
static unsigned count
Definition unittest.c:47