Home | History | Annotate | Line # | Download | only in unit
      1  1.1  christos #include "test/jemalloc_test.h"
      2  1.1  christos 
      3  1.1  christos #include "jemalloc/internal/sec.h"
      4  1.1  christos 
      5  1.1  christos typedef struct pai_test_allocator_s pai_test_allocator_t;
      6  1.1  christos struct pai_test_allocator_s {
      7  1.1  christos 	pai_t pai;
      8  1.1  christos 	bool alloc_fail;
      9  1.1  christos 	size_t alloc_count;
     10  1.1  christos 	size_t alloc_batch_count;
     11  1.1  christos 	size_t dalloc_count;
     12  1.1  christos 	size_t dalloc_batch_count;
     13  1.1  christos 	/*
     14  1.1  christos 	 * We use a simple bump allocator as the implementation.  This isn't
     15  1.1  christos 	 * *really* correct, since we may allow expansion into a subsequent
     16  1.1  christos 	 * allocation, but it's not like the SEC is really examining the
     17  1.1  christos 	 * pointers it gets back; this is mostly just helpful for debugging.
     18  1.1  christos 	 */
     19  1.1  christos 	uintptr_t next_ptr;
     20  1.1  christos 	size_t expand_count;
     21  1.1  christos 	bool expand_return_value;
     22  1.1  christos 	size_t shrink_count;
     23  1.1  christos 	bool shrink_return_value;
     24  1.1  christos };
     25  1.1  christos 
     26  1.1  christos static void
     27  1.1  christos test_sec_init(sec_t *sec, pai_t *fallback, size_t nshards, size_t max_alloc,
     28  1.1  christos     size_t max_bytes) {
     29  1.1  christos 	sec_opts_t opts;
     30  1.1  christos 	opts.nshards = 1;
     31  1.1  christos 	opts.max_alloc = max_alloc;
     32  1.1  christos 	opts.max_bytes = max_bytes;
     33  1.1  christos 	/*
     34  1.1  christos 	 * Just choose reasonable defaults for these; most tests don't care so
     35  1.1  christos 	 * long as they're something reasonable.
     36  1.1  christos 	 */
     37  1.1  christos 	opts.bytes_after_flush = max_bytes / 2;
     38  1.1  christos 	opts.batch_fill_extra = 4;
     39  1.1  christos 
     40  1.1  christos 	/*
     41  1.1  christos 	 * We end up leaking this base, but that's fine; this test is
     42  1.1  christos 	 * short-running, and SECs are arena-scoped in reality.
     43  1.1  christos 	 */
     44  1.1  christos 	base_t *base = base_new(TSDN_NULL, /* ind */ 123,
     45  1.1  christos 	    &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
     46  1.1  christos 
     47  1.1  christos 	bool err = sec_init(TSDN_NULL, sec, base, fallback, &opts);
     48  1.1  christos 	assert_false(err, "Unexpected initialization failure");
     49  1.1  christos 	assert_u_ge(sec->npsizes, 0, "Zero size classes allowed for caching");
     50  1.1  christos }
     51  1.1  christos 
     52  1.1  christos static inline edata_t *
     53  1.1  christos pai_test_allocator_alloc(tsdn_t *tsdn, pai_t *self, size_t size,
     54  1.1  christos     size_t alignment, bool zero, bool guarded, bool frequent_reuse,
     55  1.1  christos     bool *deferred_work_generated) {
     56  1.1  christos 	assert(!guarded);
     57  1.1  christos 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
     58  1.1  christos 	if (ta->alloc_fail) {
     59  1.1  christos 		return NULL;
     60  1.1  christos 	}
     61  1.1  christos 	edata_t *edata = malloc(sizeof(edata_t));
     62  1.1  christos 	assert_ptr_not_null(edata, "");
     63  1.1  christos 	ta->next_ptr += alignment - 1;
     64  1.1  christos 	edata_init(edata, /* arena_ind */ 0,
     65  1.1  christos 	    (void *)(ta->next_ptr & ~(alignment - 1)), size,
     66  1.1  christos 	    /* slab */ false,
     67  1.1  christos 	    /* szind */ 0, /* sn */ 1, extent_state_active, /* zero */ zero,
     68  1.1  christos 	    /* comitted */ true, /* ranged */ false, EXTENT_NOT_HEAD);
     69  1.1  christos 	ta->next_ptr += size;
     70  1.1  christos 	ta->alloc_count++;
     71  1.1  christos 	return edata;
     72  1.1  christos }
     73  1.1  christos 
     74  1.1  christos static inline size_t
     75  1.1  christos pai_test_allocator_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size,
     76  1.1  christos     size_t nallocs, edata_list_active_t *results,
     77  1.1  christos     bool *deferred_work_generated) {
     78  1.1  christos 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
     79  1.1  christos 	if (ta->alloc_fail) {
     80  1.1  christos 		return 0;
     81  1.1  christos 	}
     82  1.1  christos 	for (size_t i = 0; i < nallocs; i++) {
     83  1.1  christos 		edata_t *edata = malloc(sizeof(edata_t));
     84  1.1  christos 		assert_ptr_not_null(edata, "");
     85  1.1  christos 		edata_init(edata, /* arena_ind */ 0,
     86  1.1  christos 		    (void *)ta->next_ptr, size,
     87  1.1  christos 		    /* slab */ false, /* szind */ 0, /* sn */ 1,
     88  1.1  christos 		    extent_state_active, /* zero */ false, /* comitted */ true,
     89  1.1  christos 		    /* ranged */ false, EXTENT_NOT_HEAD);
     90  1.1  christos 		ta->next_ptr += size;
     91  1.1  christos 		ta->alloc_batch_count++;
     92  1.1  christos 		edata_list_active_append(results, edata);
     93  1.1  christos 	}
     94  1.1  christos 	return nallocs;
     95  1.1  christos }
     96  1.1  christos 
     97  1.1  christos static bool
     98  1.1  christos pai_test_allocator_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata,
     99  1.1  christos     size_t old_size, size_t new_size, bool zero,
    100  1.1  christos     bool *deferred_work_generated) {
    101  1.1  christos 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
    102  1.1  christos 	ta->expand_count++;
    103  1.1  christos 	return ta->expand_return_value;
    104  1.1  christos }
    105  1.1  christos 
    106  1.1  christos static bool
    107  1.1  christos pai_test_allocator_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata,
    108  1.1  christos     size_t old_size, size_t new_size, bool *deferred_work_generated) {
    109  1.1  christos 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
    110  1.1  christos 	ta->shrink_count++;
    111  1.1  christos 	return ta->shrink_return_value;
    112  1.1  christos }
    113  1.1  christos 
    114  1.1  christos static void
    115  1.1  christos pai_test_allocator_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata,
    116  1.1  christos     bool *deferred_work_generated) {
    117  1.1  christos 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
    118  1.1  christos 	ta->dalloc_count++;
    119  1.1  christos 	free(edata);
    120  1.1  christos }
    121  1.1  christos 
    122  1.1  christos static void
    123  1.1  christos pai_test_allocator_dalloc_batch(tsdn_t *tsdn, pai_t *self,
    124  1.1  christos     edata_list_active_t *list, bool *deferred_work_generated) {
    125  1.1  christos 	pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
    126  1.1  christos 
    127  1.1  christos 	edata_t *edata;
    128  1.1  christos 	while ((edata = edata_list_active_first(list)) != NULL) {
    129  1.1  christos 		edata_list_active_remove(list, edata);
    130  1.1  christos 		ta->dalloc_batch_count++;
    131  1.1  christos 		free(edata);
    132  1.1  christos 	}
    133  1.1  christos }
    134  1.1  christos 
    135  1.1  christos static inline void
    136  1.1  christos pai_test_allocator_init(pai_test_allocator_t *ta) {
    137  1.1  christos 	ta->alloc_fail = false;
    138  1.1  christos 	ta->alloc_count = 0;
    139  1.1  christos 	ta->alloc_batch_count = 0;
    140  1.1  christos 	ta->dalloc_count = 0;
    141  1.1  christos 	ta->dalloc_batch_count = 0;
    142  1.1  christos 	/* Just don't start the edata at 0. */
    143  1.1  christos 	ta->next_ptr = 10 * PAGE;
    144  1.1  christos 	ta->expand_count = 0;
    145  1.1  christos 	ta->expand_return_value = false;
    146  1.1  christos 	ta->shrink_count = 0;
    147  1.1  christos 	ta->shrink_return_value = false;
    148  1.1  christos 	ta->pai.alloc = &pai_test_allocator_alloc;
    149  1.1  christos 	ta->pai.alloc_batch = &pai_test_allocator_alloc_batch;
    150  1.1  christos 	ta->pai.expand = &pai_test_allocator_expand;
    151  1.1  christos 	ta->pai.shrink = &pai_test_allocator_shrink;
    152  1.1  christos 	ta->pai.dalloc = &pai_test_allocator_dalloc;
    153  1.1  christos 	ta->pai.dalloc_batch = &pai_test_allocator_dalloc_batch;
    154  1.1  christos }
    155  1.1  christos 
    156  1.1  christos TEST_BEGIN(test_reuse) {
    157  1.1  christos 	pai_test_allocator_t ta;
    158  1.1  christos 	pai_test_allocator_init(&ta);
    159  1.1  christos 	sec_t sec;
    160  1.1  christos 	/*
    161  1.1  christos 	 * We can't use the "real" tsd, since we malloc within the test
    162  1.1  christos 	 * allocator hooks; we'd get lock inversion crashes.  Eventually, we
    163  1.1  christos 	 * should have a way to mock tsds, but for now just don't do any
    164  1.1  christos 	 * lock-order checking.
    165  1.1  christos 	 */
    166  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    167  1.1  christos 	/*
    168  1.1  christos 	 * 11 allocs apiece of 1-PAGE and 2-PAGE objects means that we should be
    169  1.1  christos 	 * able to get to 33 pages in the cache before triggering a flush.  We
    170  1.1  christos 	 * set the flush liimt to twice this amount, to avoid accidentally
    171  1.1  christos 	 * triggering a flush caused by the batch-allocation down the cache fill
    172  1.1  christos 	 * pathway disrupting ordering.
    173  1.1  christos 	 */
    174  1.1  christos 	enum { NALLOCS = 11 };
    175  1.1  christos 	edata_t *one_page[NALLOCS];
    176  1.1  christos 	edata_t *two_page[NALLOCS];
    177  1.1  christos 	bool deferred_work_generated = false;
    178  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 2 * PAGE,
    179  1.1  christos 	    /* max_bytes */ 2 * (NALLOCS * PAGE + NALLOCS * 2 * PAGE));
    180  1.1  christos 	for (int i = 0; i < NALLOCS; i++) {
    181  1.1  christos 		one_page[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    182  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    183  1.1  christos 		    false, &deferred_work_generated);
    184  1.1  christos 		expect_ptr_not_null(one_page[i], "Unexpected alloc failure");
    185  1.1  christos 		two_page[i] = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
    186  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    187  1.1  christos 		    false, &deferred_work_generated);
    188  1.1  christos 		expect_ptr_not_null(one_page[i], "Unexpected alloc failure");
    189  1.1  christos 	}
    190  1.1  christos 	expect_zu_eq(0, ta.alloc_count, "Should be using batch allocs");
    191  1.1  christos 	size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
    192  1.1  christos 	expect_zu_le(2 * NALLOCS, max_allocs,
    193  1.1  christos 	    "Incorrect number of allocations");
    194  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    195  1.1  christos 	    "Incorrect number of allocations");
    196  1.1  christos 	/*
    197  1.1  christos 	 * Free in a different order than we allocated, to make sure free-list
    198  1.1  christos 	 * separation works correctly.
    199  1.1  christos 	 */
    200  1.1  christos 	for (int i = NALLOCS - 1; i >= 0; i--) {
    201  1.1  christos 		pai_dalloc(tsdn, &sec.pai, one_page[i],
    202  1.1  christos 		    &deferred_work_generated);
    203  1.1  christos 	}
    204  1.1  christos 	for (int i = NALLOCS - 1; i >= 0; i--) {
    205  1.1  christos 		pai_dalloc(tsdn, &sec.pai, two_page[i],
    206  1.1  christos 		    &deferred_work_generated);
    207  1.1  christos 	}
    208  1.1  christos 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
    209  1.1  christos 	    "Incorrect number of allocations");
    210  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    211  1.1  christos 	    "Incorrect number of allocations");
    212  1.1  christos 	/*
    213  1.1  christos 	 * Check that the n'th most recent deallocated extent is returned for
    214  1.1  christos 	 * the n'th alloc request of a given size.
    215  1.1  christos 	 */
    216  1.1  christos 	for (int i = 0; i < NALLOCS; i++) {
    217  1.1  christos 		edata_t *alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    218  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    219  1.1  christos 		    false, &deferred_work_generated);
    220  1.1  christos 		edata_t *alloc2 = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
    221  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    222  1.1  christos 		    false, &deferred_work_generated);
    223  1.1  christos 		expect_ptr_eq(one_page[i], alloc1,
    224  1.1  christos 		    "Got unexpected allocation");
    225  1.1  christos 		expect_ptr_eq(two_page[i], alloc2,
    226  1.1  christos 		    "Got unexpected allocation");
    227  1.1  christos 	}
    228  1.1  christos 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
    229  1.1  christos 	    "Incorrect number of allocations");
    230  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    231  1.1  christos 	    "Incorrect number of allocations");
    232  1.1  christos }
    233  1.1  christos TEST_END
    234  1.1  christos 
    235  1.1  christos 
    236  1.1  christos TEST_BEGIN(test_auto_flush) {
    237  1.1  christos 	pai_test_allocator_t ta;
    238  1.1  christos 	pai_test_allocator_init(&ta);
    239  1.1  christos 	sec_t sec;
    240  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    241  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    242  1.1  christos 	/*
    243  1.1  christos 	 * 10-allocs apiece of 1-PAGE and 2-PAGE objects means that we should be
    244  1.1  christos 	 * able to get to 30 pages in the cache before triggering a flush.  The
    245  1.1  christos 	 * choice of NALLOCS here is chosen to match the batch allocation
    246  1.1  christos 	 * default (4 extra + 1 == 5; so 10 allocations leaves the cache exactly
    247  1.1  christos 	 * empty, even in the presence of batch allocation on fill).
    248  1.1  christos 	 * Eventually, once our allocation batching strategies become smarter,
    249  1.1  christos 	 * this should change.
    250  1.1  christos 	 */
    251  1.1  christos 	enum { NALLOCS = 10 };
    252  1.1  christos 	edata_t *extra_alloc;
    253  1.1  christos 	edata_t *allocs[NALLOCS];
    254  1.1  christos 	bool deferred_work_generated = false;
    255  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
    256  1.1  christos 	    /* max_bytes */ NALLOCS * PAGE);
    257  1.1  christos 	for (int i = 0; i < NALLOCS; i++) {
    258  1.1  christos 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    259  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    260  1.1  christos 		    false, &deferred_work_generated);
    261  1.1  christos 		expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
    262  1.1  christos 	}
    263  1.1  christos 	extra_alloc = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
    264  1.1  christos 	    /* guarded */ false, /* frequent_reuse */ false,
    265  1.1  christos 	    &deferred_work_generated);
    266  1.1  christos 	expect_ptr_not_null(extra_alloc, "Unexpected alloc failure");
    267  1.1  christos 	size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
    268  1.1  christos 	expect_zu_le(NALLOCS + 1, max_allocs,
    269  1.1  christos 	    "Incorrect number of allocations");
    270  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    271  1.1  christos 	    "Incorrect number of allocations");
    272  1.1  christos 	/* Free until the SEC is full, but should not have flushed yet. */
    273  1.1  christos 	for (int i = 0; i < NALLOCS; i++) {
    274  1.1  christos 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
    275  1.1  christos 	}
    276  1.1  christos 	expect_zu_le(NALLOCS + 1, max_allocs,
    277  1.1  christos 	    "Incorrect number of allocations");
    278  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    279  1.1  christos 	    "Incorrect number of allocations");
    280  1.1  christos 	/*
    281  1.1  christos 	 * Free the extra allocation; this should trigger a flush.  The internal
    282  1.1  christos 	 * flushing logic is allowed to get complicated; for now, we rely on our
    283  1.1  christos 	 * whitebox knowledge of the fact that the SEC flushes bins in their
    284  1.1  christos 	 * entirety when it decides to do so, and it has only one bin active
    285  1.1  christos 	 * right now.
    286  1.1  christos 	 */
    287  1.1  christos 	pai_dalloc(tsdn, &sec.pai, extra_alloc, &deferred_work_generated);
    288  1.1  christos 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
    289  1.1  christos 	    "Incorrect number of allocations");
    290  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    291  1.1  christos 	    "Incorrect number of (non-batch) deallocations");
    292  1.1  christos 	expect_zu_eq(NALLOCS + 1, ta.dalloc_batch_count,
    293  1.1  christos 	    "Incorrect number of batch deallocations");
    294  1.1  christos }
    295  1.1  christos TEST_END
    296  1.1  christos 
    297  1.1  christos /*
    298  1.1  christos  * A disable and a flush are *almost* equivalent; the only difference is what
    299  1.1  christos  * happens afterwards; disabling disallows all future caching as well.
    300  1.1  christos  */
    301  1.1  christos static void
    302  1.1  christos do_disable_flush_test(bool is_disable) {
    303  1.1  christos 	pai_test_allocator_t ta;
    304  1.1  christos 	pai_test_allocator_init(&ta);
    305  1.1  christos 	sec_t sec;
    306  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    307  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    308  1.1  christos 
    309  1.1  christos 	enum { NALLOCS = 11 };
    310  1.1  christos 	edata_t *allocs[NALLOCS];
    311  1.1  christos 	bool deferred_work_generated = false;
    312  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
    313  1.1  christos 	    /* max_bytes */ NALLOCS * PAGE);
    314  1.1  christos 	for (int i = 0; i < NALLOCS; i++) {
    315  1.1  christos 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    316  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    317  1.1  christos 		    false, &deferred_work_generated);
    318  1.1  christos 		expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
    319  1.1  christos 	}
    320  1.1  christos 	/* Free all but the last aloc. */
    321  1.1  christos 	for (int i = 0; i < NALLOCS - 1; i++) {
    322  1.1  christos 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
    323  1.1  christos 	}
    324  1.1  christos 	size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
    325  1.1  christos 
    326  1.1  christos 	expect_zu_le(NALLOCS, max_allocs, "Incorrect number of allocations");
    327  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    328  1.1  christos 	    "Incorrect number of allocations");
    329  1.1  christos 
    330  1.1  christos 	if (is_disable) {
    331  1.1  christos 		sec_disable(tsdn, &sec);
    332  1.1  christos 	} else {
    333  1.1  christos 		sec_flush(tsdn, &sec);
    334  1.1  christos 	}
    335  1.1  christos 
    336  1.1  christos 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
    337  1.1  christos 	    "Incorrect number of allocations");
    338  1.1  christos 	expect_zu_eq(0, ta.dalloc_count,
    339  1.1  christos 	    "Incorrect number of (non-batch) deallocations");
    340  1.1  christos 	expect_zu_le(NALLOCS - 1, ta.dalloc_batch_count,
    341  1.1  christos 	    "Incorrect number of batch deallocations");
    342  1.1  christos 	size_t old_dalloc_batch_count = ta.dalloc_batch_count;
    343  1.1  christos 
    344  1.1  christos 	/*
    345  1.1  christos 	 * If we free into a disabled SEC, it should forward to the fallback.
    346  1.1  christos 	 * Otherwise, the SEC should accept the allocation.
    347  1.1  christos 	 */
    348  1.1  christos 	pai_dalloc(tsdn, &sec.pai, allocs[NALLOCS - 1],
    349  1.1  christos 	    &deferred_work_generated);
    350  1.1  christos 
    351  1.1  christos 	expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
    352  1.1  christos 	    "Incorrect number of allocations");
    353  1.1  christos 	expect_zu_eq(is_disable ? 1 : 0, ta.dalloc_count,
    354  1.1  christos 	    "Incorrect number of (non-batch) deallocations");
    355  1.1  christos 	expect_zu_eq(old_dalloc_batch_count, ta.dalloc_batch_count,
    356  1.1  christos 	    "Incorrect number of batch deallocations");
    357  1.1  christos }
    358  1.1  christos 
    359  1.1  christos TEST_BEGIN(test_disable) {
    360  1.1  christos 	do_disable_flush_test(/* is_disable */ true);
    361  1.1  christos }
    362  1.1  christos TEST_END
    363  1.1  christos 
    364  1.1  christos TEST_BEGIN(test_flush) {
    365  1.1  christos 	do_disable_flush_test(/* is_disable */ false);
    366  1.1  christos }
    367  1.1  christos TEST_END
    368  1.1  christos 
    369  1.1  christos TEST_BEGIN(test_max_alloc_respected) {
    370  1.1  christos 	pai_test_allocator_t ta;
    371  1.1  christos 	pai_test_allocator_init(&ta);
    372  1.1  christos 	sec_t sec;
    373  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    374  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    375  1.1  christos 
    376  1.1  christos 	size_t max_alloc = 2 * PAGE;
    377  1.1  christos 	size_t attempted_alloc = 3 * PAGE;
    378  1.1  christos 
    379  1.1  christos 	bool deferred_work_generated = false;
    380  1.1  christos 
    381  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, max_alloc,
    382  1.1  christos 	    /* max_bytes */ 1000 * PAGE);
    383  1.1  christos 
    384  1.1  christos 	for (size_t i = 0; i < 100; i++) {
    385  1.1  christos 		expect_zu_eq(i, ta.alloc_count,
    386  1.1  christos 		    "Incorrect number of allocations");
    387  1.1  christos 		expect_zu_eq(i, ta.dalloc_count,
    388  1.1  christos 		    "Incorrect number of deallocations");
    389  1.1  christos 		edata_t *edata = pai_alloc(tsdn, &sec.pai, attempted_alloc,
    390  1.1  christos 		    PAGE, /* zero */ false, /* guarded */ false,
    391  1.1  christos 		    /* frequent_reuse */ false, &deferred_work_generated);
    392  1.1  christos 		expect_ptr_not_null(edata, "Unexpected alloc failure");
    393  1.1  christos 		expect_zu_eq(i + 1, ta.alloc_count,
    394  1.1  christos 		    "Incorrect number of allocations");
    395  1.1  christos 		expect_zu_eq(i, ta.dalloc_count,
    396  1.1  christos 		    "Incorrect number of deallocations");
    397  1.1  christos 		pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
    398  1.1  christos 	}
    399  1.1  christos }
    400  1.1  christos TEST_END
    401  1.1  christos 
    402  1.1  christos TEST_BEGIN(test_expand_shrink_delegate) {
    403  1.1  christos 	/*
    404  1.1  christos 	 * Expand and shrink shouldn't affect sec state; they should just
    405  1.1  christos 	 * delegate to the fallback PAI.
    406  1.1  christos 	 */
    407  1.1  christos 	pai_test_allocator_t ta;
    408  1.1  christos 	pai_test_allocator_init(&ta);
    409  1.1  christos 	sec_t sec;
    410  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    411  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    412  1.1  christos 
    413  1.1  christos 	bool deferred_work_generated = false;
    414  1.1  christos 
    415  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 10 * PAGE,
    416  1.1  christos 	    /* max_bytes */ 1000 * PAGE);
    417  1.1  christos 	edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    418  1.1  christos 	    /* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
    419  1.1  christos 	    &deferred_work_generated);
    420  1.1  christos 	expect_ptr_not_null(edata, "Unexpected alloc failure");
    421  1.1  christos 
    422  1.1  christos 	bool err = pai_expand(tsdn, &sec.pai, edata, PAGE, 4 * PAGE,
    423  1.1  christos 	    /* zero */ false, &deferred_work_generated);
    424  1.1  christos 	expect_false(err, "Unexpected expand failure");
    425  1.1  christos 	expect_zu_eq(1, ta.expand_count, "");
    426  1.1  christos 	ta.expand_return_value = true;
    427  1.1  christos 	err = pai_expand(tsdn, &sec.pai, edata, 4 * PAGE, 3 * PAGE,
    428  1.1  christos 	    /* zero */ false, &deferred_work_generated);
    429  1.1  christos 	expect_true(err, "Unexpected expand success");
    430  1.1  christos 	expect_zu_eq(2, ta.expand_count, "");
    431  1.1  christos 
    432  1.1  christos 	err = pai_shrink(tsdn, &sec.pai, edata, 4 * PAGE, 2 * PAGE,
    433  1.1  christos 	    &deferred_work_generated);
    434  1.1  christos 	expect_false(err, "Unexpected shrink failure");
    435  1.1  christos 	expect_zu_eq(1, ta.shrink_count, "");
    436  1.1  christos 	ta.shrink_return_value = true;
    437  1.1  christos 	err = pai_shrink(tsdn, &sec.pai, edata, 2 * PAGE, PAGE,
    438  1.1  christos 	    &deferred_work_generated);
    439  1.1  christos 	expect_true(err, "Unexpected shrink success");
    440  1.1  christos 	expect_zu_eq(2, ta.shrink_count, "");
    441  1.1  christos }
    442  1.1  christos TEST_END
    443  1.1  christos 
    444  1.1  christos TEST_BEGIN(test_nshards_0) {
    445  1.1  christos 	pai_test_allocator_t ta;
    446  1.1  christos 	pai_test_allocator_init(&ta);
    447  1.1  christos 	sec_t sec;
    448  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    449  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    450  1.1  christos 	base_t *base = base_new(TSDN_NULL, /* ind */ 123,
    451  1.1  christos 	    &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
    452  1.1  christos 
    453  1.1  christos 	sec_opts_t opts = SEC_OPTS_DEFAULT;
    454  1.1  christos 	opts.nshards = 0;
    455  1.1  christos 	sec_init(TSDN_NULL, &sec, base, &ta.pai, &opts);
    456  1.1  christos 
    457  1.1  christos 	bool deferred_work_generated = false;
    458  1.1  christos 	edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    459  1.1  christos 	    /* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
    460  1.1  christos 	    &deferred_work_generated);
    461  1.1  christos 	pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
    462  1.1  christos 
    463  1.1  christos 	/* Both operations should have gone directly to the fallback. */
    464  1.1  christos 	expect_zu_eq(1, ta.alloc_count, "");
    465  1.1  christos 	expect_zu_eq(1, ta.dalloc_count, "");
    466  1.1  christos }
    467  1.1  christos TEST_END
    468  1.1  christos 
    469  1.1  christos static void
    470  1.1  christos expect_stats_pages(tsdn_t *tsdn, sec_t *sec, size_t npages) {
    471  1.1  christos 	sec_stats_t stats;
    472  1.1  christos 	/*
    473  1.1  christos 	 * Check that the stats merging accumulates rather than overwrites by
    474  1.1  christos 	 * putting some (made up) data there to begin with.
    475  1.1  christos 	 */
    476  1.1  christos 	stats.bytes = 123;
    477  1.1  christos 	sec_stats_merge(tsdn, sec, &stats);
    478  1.1  christos 	assert_zu_le(npages * PAGE + 123, stats.bytes, "");
    479  1.1  christos }
    480  1.1  christos 
    481  1.1  christos TEST_BEGIN(test_stats_simple) {
    482  1.1  christos 	pai_test_allocator_t ta;
    483  1.1  christos 	pai_test_allocator_init(&ta);
    484  1.1  christos 	sec_t sec;
    485  1.1  christos 
    486  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    487  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    488  1.1  christos 
    489  1.1  christos 	enum {
    490  1.1  christos 		NITERS = 100,
    491  1.1  christos 		FLUSH_PAGES = 20,
    492  1.1  christos 	};
    493  1.1  christos 
    494  1.1  christos 	bool deferred_work_generated = false;
    495  1.1  christos 
    496  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
    497  1.1  christos 	    /* max_bytes */ FLUSH_PAGES * PAGE);
    498  1.1  christos 
    499  1.1  christos 	edata_t *allocs[FLUSH_PAGES];
    500  1.1  christos 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
    501  1.1  christos 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    502  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    503  1.1  christos 		    false, &deferred_work_generated);
    504  1.1  christos 		expect_stats_pages(tsdn, &sec, 0);
    505  1.1  christos 	}
    506  1.1  christos 
    507  1.1  christos 	/* Increase and decrease, without flushing. */
    508  1.1  christos 	for (size_t i = 0; i < NITERS; i++) {
    509  1.1  christos 		for (size_t j = 0; j < FLUSH_PAGES / 2; j++) {
    510  1.1  christos 			pai_dalloc(tsdn, &sec.pai, allocs[j],
    511  1.1  christos 			    &deferred_work_generated);
    512  1.1  christos 			expect_stats_pages(tsdn, &sec, j + 1);
    513  1.1  christos 		}
    514  1.1  christos 		for (size_t j = 0; j < FLUSH_PAGES / 2; j++) {
    515  1.1  christos 			allocs[j] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    516  1.1  christos 			    /* zero */ false, /* guarded */ false,
    517  1.1  christos 			    /* frequent_reuse */ false,
    518  1.1  christos 			    &deferred_work_generated);
    519  1.1  christos 			expect_stats_pages(tsdn, &sec, FLUSH_PAGES / 2 - j - 1);
    520  1.1  christos 		}
    521  1.1  christos 	}
    522  1.1  christos }
    523  1.1  christos TEST_END
    524  1.1  christos 
    525  1.1  christos TEST_BEGIN(test_stats_auto_flush) {
    526  1.1  christos 	pai_test_allocator_t ta;
    527  1.1  christos 	pai_test_allocator_init(&ta);
    528  1.1  christos 	sec_t sec;
    529  1.1  christos 
    530  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    531  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    532  1.1  christos 
    533  1.1  christos 	enum {
    534  1.1  christos 		FLUSH_PAGES = 10,
    535  1.1  christos 	};
    536  1.1  christos 
    537  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
    538  1.1  christos 	    /* max_bytes */ FLUSH_PAGES * PAGE);
    539  1.1  christos 
    540  1.1  christos 	edata_t *extra_alloc0;
    541  1.1  christos 	edata_t *extra_alloc1;
    542  1.1  christos 	edata_t *allocs[2 * FLUSH_PAGES];
    543  1.1  christos 
    544  1.1  christos 	bool deferred_work_generated = false;
    545  1.1  christos 
    546  1.1  christos 	extra_alloc0 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
    547  1.1  christos 	    /* guarded */ false, /* frequent_reuse */ false,
    548  1.1  christos 	    &deferred_work_generated);
    549  1.1  christos 	extra_alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
    550  1.1  christos 	    /* guarded */ false, /* frequent_reuse */ false,
    551  1.1  christos 	    &deferred_work_generated);
    552  1.1  christos 
    553  1.1  christos 	for (size_t i = 0; i < 2 * FLUSH_PAGES; i++) {
    554  1.1  christos 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    555  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    556  1.1  christos 		    false, &deferred_work_generated);
    557  1.1  christos 	}
    558  1.1  christos 
    559  1.1  christos 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
    560  1.1  christos 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
    561  1.1  christos 	}
    562  1.1  christos 	pai_dalloc(tsdn, &sec.pai, extra_alloc0, &deferred_work_generated);
    563  1.1  christos 
    564  1.1  christos 	/* Flush the remaining pages; stats should still work. */
    565  1.1  christos 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
    566  1.1  christos 		pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES + i],
    567  1.1  christos 		    &deferred_work_generated);
    568  1.1  christos 	}
    569  1.1  christos 
    570  1.1  christos 	pai_dalloc(tsdn, &sec.pai, extra_alloc1, &deferred_work_generated);
    571  1.1  christos 
    572  1.1  christos 	expect_stats_pages(tsdn, &sec, ta.alloc_count + ta.alloc_batch_count
    573  1.1  christos 	    - ta.dalloc_count - ta.dalloc_batch_count);
    574  1.1  christos }
    575  1.1  christos TEST_END
    576  1.1  christos 
    577  1.1  christos TEST_BEGIN(test_stats_manual_flush) {
    578  1.1  christos 	pai_test_allocator_t ta;
    579  1.1  christos 	pai_test_allocator_init(&ta);
    580  1.1  christos 	sec_t sec;
    581  1.1  christos 
    582  1.1  christos 	/* See the note above -- we can't use the real tsd. */
    583  1.1  christos 	tsdn_t *tsdn = TSDN_NULL;
    584  1.1  christos 
    585  1.1  christos 	enum {
    586  1.1  christos 		FLUSH_PAGES = 10,
    587  1.1  christos 	};
    588  1.1  christos 
    589  1.1  christos 	test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
    590  1.1  christos 	    /* max_bytes */ FLUSH_PAGES * PAGE);
    591  1.1  christos 
    592  1.1  christos 	bool deferred_work_generated = false;
    593  1.1  christos 	edata_t *allocs[FLUSH_PAGES];
    594  1.1  christos 	for (size_t i = 0; i < FLUSH_PAGES; i++) {
    595  1.1  christos 		allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
    596  1.1  christos 		    /* zero */ false, /* guarded */ false, /* frequent_reuse */
    597  1.1  christos 		    false, &deferred_work_generated);
    598  1.1  christos 		expect_stats_pages(tsdn, &sec, 0);
    599  1.1  christos 	}
    600  1.1  christos 
    601  1.1  christos 	/* Dalloc the first half of the allocations. */
    602  1.1  christos 	for (size_t i = 0; i < FLUSH_PAGES / 2; i++) {
    603  1.1  christos 		pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
    604  1.1  christos 		expect_stats_pages(tsdn, &sec, i + 1);
    605  1.1  christos 	}
    606  1.1  christos 
    607  1.1  christos 	sec_flush(tsdn, &sec);
    608  1.1  christos 	expect_stats_pages(tsdn, &sec, 0);
    609  1.1  christos 
    610  1.1  christos 	/* Flush the remaining pages. */
    611  1.1  christos 	for (size_t i = 0; i < FLUSH_PAGES / 2; i++) {
    612  1.1  christos 		pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES / 2 + i],
    613  1.1  christos 		    &deferred_work_generated);
    614  1.1  christos 		expect_stats_pages(tsdn, &sec, i + 1);
    615  1.1  christos 	}
    616  1.1  christos 	sec_disable(tsdn, &sec);
    617  1.1  christos 	expect_stats_pages(tsdn, &sec, 0);
    618  1.1  christos }
    619  1.1  christos TEST_END
    620  1.1  christos 
    621  1.1  christos int
    622  1.1  christos main(void) {
    623  1.1  christos 	return test(
    624  1.1  christos 	    test_reuse,
    625  1.1  christos 	    test_auto_flush,
    626  1.1  christos 	    test_disable,
    627  1.1  christos 	    test_flush,
    628  1.1  christos 	    test_max_alloc_respected,
    629  1.1  christos 	    test_expand_shrink_delegate,
    630  1.1  christos 	    test_nshards_0,
    631  1.1  christos 	    test_stats_simple,
    632  1.1  christos 	    test_stats_auto_flush,
    633  1.1  christos 	    test_stats_manual_flush);
    634  1.1  christos }
    635