f56f5b9930
Currently used only for guarding purposes, the hint is used to determine if the allocation is supposed to be frequently reused. For example, it might urge the allocator to ensure the allocation is cached.
634 lines
20 KiB
C
634 lines
20 KiB
C
#include "test/jemalloc_test.h"
|
|
|
|
#include "jemalloc/internal/sec.h"
|
|
|
|
typedef struct pai_test_allocator_s pai_test_allocator_t;
|
|
struct pai_test_allocator_s {
|
|
pai_t pai;
|
|
bool alloc_fail;
|
|
size_t alloc_count;
|
|
size_t alloc_batch_count;
|
|
size_t dalloc_count;
|
|
size_t dalloc_batch_count;
|
|
/*
|
|
* We use a simple bump allocator as the implementation. This isn't
|
|
* *really* correct, since we may allow expansion into a subsequent
|
|
* allocation, but it's not like the SEC is really examining the
|
|
* pointers it gets back; this is mostly just helpful for debugging.
|
|
*/
|
|
uintptr_t next_ptr;
|
|
size_t expand_count;
|
|
bool expand_return_value;
|
|
size_t shrink_count;
|
|
bool shrink_return_value;
|
|
};
|
|
|
|
static void
|
|
test_sec_init(sec_t *sec, pai_t *fallback, size_t nshards, size_t max_alloc,
|
|
size_t max_bytes) {
|
|
sec_opts_t opts;
|
|
opts.nshards = 1;
|
|
opts.max_alloc = max_alloc;
|
|
opts.max_bytes = max_bytes;
|
|
/*
|
|
* Just choose reasonable defaults for these; most tests don't care so
|
|
* long as they're something reasonable.
|
|
*/
|
|
opts.bytes_after_flush = max_bytes / 2;
|
|
opts.batch_fill_extra = 4;
|
|
|
|
/*
|
|
* We end up leaking this base, but that's fine; this test is
|
|
* short-running, and SECs are arena-scoped in reality.
|
|
*/
|
|
base_t *base = base_new(TSDN_NULL, /* ind */ 123,
|
|
&ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
|
|
|
|
bool err = sec_init(TSDN_NULL, sec, base, fallback, &opts);
|
|
assert_false(err, "Unexpected initialization failure");
|
|
}
|
|
|
|
static inline edata_t *
|
|
pai_test_allocator_alloc(tsdn_t *tsdn, pai_t *self, size_t size,
|
|
size_t alignment, bool zero, bool guarded, bool frequent_reuse,
|
|
bool *deferred_work_generated) {
|
|
assert(!guarded);
|
|
pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
|
|
if (ta->alloc_fail) {
|
|
return NULL;
|
|
}
|
|
edata_t *edata = malloc(sizeof(edata_t));
|
|
assert_ptr_not_null(edata, "");
|
|
ta->next_ptr += alignment - 1;
|
|
edata_init(edata, /* arena_ind */ 0,
|
|
(void *)(ta->next_ptr & ~(alignment - 1)), size,
|
|
/* slab */ false,
|
|
/* szind */ 0, /* sn */ 1, extent_state_active, /* zero */ zero,
|
|
/* comitted */ true, /* ranged */ false, EXTENT_NOT_HEAD);
|
|
ta->next_ptr += size;
|
|
ta->alloc_count++;
|
|
return edata;
|
|
}
|
|
|
|
static inline size_t
|
|
pai_test_allocator_alloc_batch(tsdn_t *tsdn, pai_t *self, size_t size,
|
|
size_t nallocs, edata_list_active_t *results,
|
|
bool *deferred_work_generated) {
|
|
pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
|
|
if (ta->alloc_fail) {
|
|
return 0;
|
|
}
|
|
for (size_t i = 0; i < nallocs; i++) {
|
|
edata_t *edata = malloc(sizeof(edata_t));
|
|
assert_ptr_not_null(edata, "");
|
|
edata_init(edata, /* arena_ind */ 0,
|
|
(void *)ta->next_ptr, size,
|
|
/* slab */ false, /* szind */ 0, /* sn */ 1,
|
|
extent_state_active, /* zero */ false, /* comitted */ true,
|
|
/* ranged */ false, EXTENT_NOT_HEAD);
|
|
ta->next_ptr += size;
|
|
ta->alloc_batch_count++;
|
|
edata_list_active_append(results, edata);
|
|
}
|
|
return nallocs;
|
|
}
|
|
|
|
static bool
|
|
pai_test_allocator_expand(tsdn_t *tsdn, pai_t *self, edata_t *edata,
|
|
size_t old_size, size_t new_size, bool zero,
|
|
bool *deferred_work_generated) {
|
|
pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
|
|
ta->expand_count++;
|
|
return ta->expand_return_value;
|
|
}
|
|
|
|
static bool
|
|
pai_test_allocator_shrink(tsdn_t *tsdn, pai_t *self, edata_t *edata,
|
|
size_t old_size, size_t new_size, bool *deferred_work_generated) {
|
|
pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
|
|
ta->shrink_count++;
|
|
return ta->shrink_return_value;
|
|
}
|
|
|
|
static void
|
|
pai_test_allocator_dalloc(tsdn_t *tsdn, pai_t *self, edata_t *edata,
|
|
bool *deferred_work_generated) {
|
|
pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
|
|
ta->dalloc_count++;
|
|
free(edata);
|
|
}
|
|
|
|
static void
|
|
pai_test_allocator_dalloc_batch(tsdn_t *tsdn, pai_t *self,
|
|
edata_list_active_t *list, bool *deferred_work_generated) {
|
|
pai_test_allocator_t *ta = (pai_test_allocator_t *)self;
|
|
|
|
edata_t *edata;
|
|
while ((edata = edata_list_active_first(list)) != NULL) {
|
|
edata_list_active_remove(list, edata);
|
|
ta->dalloc_batch_count++;
|
|
free(edata);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
pai_test_allocator_init(pai_test_allocator_t *ta) {
|
|
ta->alloc_fail = false;
|
|
ta->alloc_count = 0;
|
|
ta->alloc_batch_count = 0;
|
|
ta->dalloc_count = 0;
|
|
ta->dalloc_batch_count = 0;
|
|
/* Just don't start the edata at 0. */
|
|
ta->next_ptr = 10 * PAGE;
|
|
ta->expand_count = 0;
|
|
ta->expand_return_value = false;
|
|
ta->shrink_count = 0;
|
|
ta->shrink_return_value = false;
|
|
ta->pai.alloc = &pai_test_allocator_alloc;
|
|
ta->pai.alloc_batch = &pai_test_allocator_alloc_batch;
|
|
ta->pai.expand = &pai_test_allocator_expand;
|
|
ta->pai.shrink = &pai_test_allocator_shrink;
|
|
ta->pai.dalloc = &pai_test_allocator_dalloc;
|
|
ta->pai.dalloc_batch = &pai_test_allocator_dalloc_batch;
|
|
}
|
|
|
|
TEST_BEGIN(test_reuse) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
/*
|
|
* We can't use the "real" tsd, since we malloc within the test
|
|
* allocator hooks; we'd get lock inversion crashes. Eventually, we
|
|
* should have a way to mock tsds, but for now just don't do any
|
|
* lock-order checking.
|
|
*/
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
/*
|
|
* 11 allocs apiece of 1-PAGE and 2-PAGE objects means that we should be
|
|
* able to get to 33 pages in the cache before triggering a flush. We
|
|
* set the flush liimt to twice this amount, to avoid accidentally
|
|
* triggering a flush caused by the batch-allocation down the cache fill
|
|
* pathway disrupting ordering.
|
|
*/
|
|
enum { NALLOCS = 11 };
|
|
edata_t *one_page[NALLOCS];
|
|
edata_t *two_page[NALLOCS];
|
|
bool deferred_work_generated = false;
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 2 * PAGE,
|
|
/* max_bytes */ 2 * (NALLOCS * PAGE + NALLOCS * 2 * PAGE));
|
|
for (int i = 0; i < NALLOCS; i++) {
|
|
one_page[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_ptr_not_null(one_page[i], "Unexpected alloc failure");
|
|
two_page[i] = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_ptr_not_null(one_page[i], "Unexpected alloc failure");
|
|
}
|
|
expect_zu_eq(0, ta.alloc_count, "Should be using batch allocs");
|
|
size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
|
|
expect_zu_le(2 * NALLOCS, max_allocs,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of allocations");
|
|
/*
|
|
* Free in a different order than we allocated, to make sure free-list
|
|
* separation works correctly.
|
|
*/
|
|
for (int i = NALLOCS - 1; i >= 0; i--) {
|
|
pai_dalloc(tsdn, &sec.pai, one_page[i],
|
|
&deferred_work_generated);
|
|
}
|
|
for (int i = NALLOCS - 1; i >= 0; i--) {
|
|
pai_dalloc(tsdn, &sec.pai, two_page[i],
|
|
&deferred_work_generated);
|
|
}
|
|
expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of allocations");
|
|
/*
|
|
* Check that the n'th most recent deallocated extent is returned for
|
|
* the n'th alloc request of a given size.
|
|
*/
|
|
for (int i = 0; i < NALLOCS; i++) {
|
|
edata_t *alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
edata_t *alloc2 = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_ptr_eq(one_page[i], alloc1,
|
|
"Got unexpected allocation");
|
|
expect_ptr_eq(two_page[i], alloc2,
|
|
"Got unexpected allocation");
|
|
}
|
|
expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of allocations");
|
|
}
|
|
TEST_END
|
|
|
|
|
|
TEST_BEGIN(test_auto_flush) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
/*
|
|
* 10-allocs apiece of 1-PAGE and 2-PAGE objects means that we should be
|
|
* able to get to 30 pages in the cache before triggering a flush. The
|
|
* choice of NALLOCS here is chosen to match the batch allocation
|
|
* default (4 extra + 1 == 5; so 10 allocations leaves the cache exactly
|
|
* empty, even in the presence of batch allocation on fill).
|
|
* Eventually, once our allocation batching strategies become smarter,
|
|
* this should change.
|
|
*/
|
|
enum { NALLOCS = 10 };
|
|
edata_t *extra_alloc;
|
|
edata_t *allocs[NALLOCS];
|
|
bool deferred_work_generated = false;
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
|
|
/* max_bytes */ NALLOCS * PAGE);
|
|
for (int i = 0; i < NALLOCS; i++) {
|
|
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
|
|
}
|
|
extra_alloc = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
|
|
/* guarded */ false, /* frequent_reuse */ false,
|
|
&deferred_work_generated);
|
|
expect_ptr_not_null(extra_alloc, "Unexpected alloc failure");
|
|
size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
|
|
expect_zu_le(NALLOCS + 1, max_allocs,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of allocations");
|
|
/* Free until the SEC is full, but should not have flushed yet. */
|
|
for (int i = 0; i < NALLOCS; i++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
|
|
}
|
|
expect_zu_le(NALLOCS + 1, max_allocs,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of allocations");
|
|
/*
|
|
* Free the extra allocation; this should trigger a flush. The internal
|
|
* flushing logic is allowed to get complicated; for now, we rely on our
|
|
* whitebox knowledge of the fact that the SEC flushes bins in their
|
|
* entirety when it decides to do so, and it has only one bin active
|
|
* right now.
|
|
*/
|
|
pai_dalloc(tsdn, &sec.pai, extra_alloc, &deferred_work_generated);
|
|
expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of (non-batch) deallocations");
|
|
expect_zu_eq(NALLOCS + 1, ta.dalloc_batch_count,
|
|
"Incorrect number of batch deallocations");
|
|
}
|
|
TEST_END
|
|
|
|
/*
|
|
* A disable and a flush are *almost* equivalent; the only difference is what
|
|
* happens afterwards; disabling disallows all future caching as well.
|
|
*/
|
|
static void
|
|
do_disable_flush_test(bool is_disable) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
|
|
enum { NALLOCS = 11 };
|
|
edata_t *allocs[NALLOCS];
|
|
bool deferred_work_generated = false;
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
|
|
/* max_bytes */ NALLOCS * PAGE);
|
|
for (int i = 0; i < NALLOCS; i++) {
|
|
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
|
|
}
|
|
/* Free all but the last aloc. */
|
|
for (int i = 0; i < NALLOCS - 1; i++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
|
|
}
|
|
size_t max_allocs = ta.alloc_count + ta.alloc_batch_count;
|
|
|
|
expect_zu_le(NALLOCS, max_allocs, "Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of allocations");
|
|
|
|
if (is_disable) {
|
|
sec_disable(tsdn, &sec);
|
|
} else {
|
|
sec_flush(tsdn, &sec);
|
|
}
|
|
|
|
expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(0, ta.dalloc_count,
|
|
"Incorrect number of (non-batch) deallocations");
|
|
expect_zu_le(NALLOCS - 1, ta.dalloc_batch_count,
|
|
"Incorrect number of batch deallocations");
|
|
size_t old_dalloc_batch_count = ta.dalloc_batch_count;
|
|
|
|
/*
|
|
* If we free into a disabled SEC, it should forward to the fallback.
|
|
* Otherwise, the SEC should accept the allocation.
|
|
*/
|
|
pai_dalloc(tsdn, &sec.pai, allocs[NALLOCS - 1],
|
|
&deferred_work_generated);
|
|
|
|
expect_zu_eq(max_allocs, ta.alloc_count + ta.alloc_batch_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(is_disable ? 1 : 0, ta.dalloc_count,
|
|
"Incorrect number of (non-batch) deallocations");
|
|
expect_zu_eq(old_dalloc_batch_count, ta.dalloc_batch_count,
|
|
"Incorrect number of batch deallocations");
|
|
}
|
|
|
|
TEST_BEGIN(test_disable) {
|
|
do_disable_flush_test(/* is_disable */ true);
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_flush) {
|
|
do_disable_flush_test(/* is_disable */ false);
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_max_alloc_respected) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
|
|
size_t max_alloc = 2 * PAGE;
|
|
size_t attempted_alloc = 3 * PAGE;
|
|
|
|
bool deferred_work_generated = false;
|
|
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, max_alloc,
|
|
/* max_bytes */ 1000 * PAGE);
|
|
|
|
for (size_t i = 0; i < 100; i++) {
|
|
expect_zu_eq(i, ta.alloc_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(i, ta.dalloc_count,
|
|
"Incorrect number of deallocations");
|
|
edata_t *edata = pai_alloc(tsdn, &sec.pai, attempted_alloc,
|
|
PAGE, /* zero */ false, /* guarded */ false,
|
|
/* frequent_reuse */ false, &deferred_work_generated);
|
|
expect_ptr_not_null(edata, "Unexpected alloc failure");
|
|
expect_zu_eq(i + 1, ta.alloc_count,
|
|
"Incorrect number of allocations");
|
|
expect_zu_eq(i, ta.dalloc_count,
|
|
"Incorrect number of deallocations");
|
|
pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
|
|
}
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_expand_shrink_delegate) {
|
|
/*
|
|
* Expand and shrink shouldn't affect sec state; they should just
|
|
* delegate to the fallback PAI.
|
|
*/
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
|
|
bool deferred_work_generated = false;
|
|
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ 10 * PAGE,
|
|
/* max_bytes */ 1000 * PAGE);
|
|
edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
|
|
&deferred_work_generated);
|
|
expect_ptr_not_null(edata, "Unexpected alloc failure");
|
|
|
|
bool err = pai_expand(tsdn, &sec.pai, edata, PAGE, 4 * PAGE,
|
|
/* zero */ false, &deferred_work_generated);
|
|
expect_false(err, "Unexpected expand failure");
|
|
expect_zu_eq(1, ta.expand_count, "");
|
|
ta.expand_return_value = true;
|
|
err = pai_expand(tsdn, &sec.pai, edata, 4 * PAGE, 3 * PAGE,
|
|
/* zero */ false, &deferred_work_generated);
|
|
expect_true(err, "Unexpected expand success");
|
|
expect_zu_eq(2, ta.expand_count, "");
|
|
|
|
err = pai_shrink(tsdn, &sec.pai, edata, 4 * PAGE, 2 * PAGE,
|
|
&deferred_work_generated);
|
|
expect_false(err, "Unexpected shrink failure");
|
|
expect_zu_eq(1, ta.shrink_count, "");
|
|
ta.shrink_return_value = true;
|
|
err = pai_shrink(tsdn, &sec.pai, edata, 2 * PAGE, PAGE,
|
|
&deferred_work_generated);
|
|
expect_true(err, "Unexpected shrink success");
|
|
expect_zu_eq(2, ta.shrink_count, "");
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_nshards_0) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
base_t *base = base_new(TSDN_NULL, /* ind */ 123,
|
|
&ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
|
|
|
|
sec_opts_t opts = SEC_OPTS_DEFAULT;
|
|
opts.nshards = 0;
|
|
sec_init(TSDN_NULL, &sec, base, &ta.pai, &opts);
|
|
|
|
bool deferred_work_generated = false;
|
|
edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */ false,
|
|
&deferred_work_generated);
|
|
pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
|
|
|
|
/* Both operations should have gone directly to the fallback. */
|
|
expect_zu_eq(1, ta.alloc_count, "");
|
|
expect_zu_eq(1, ta.dalloc_count, "");
|
|
}
|
|
TEST_END
|
|
|
|
static void
|
|
expect_stats_pages(tsdn_t *tsdn, sec_t *sec, size_t npages) {
|
|
sec_stats_t stats;
|
|
/*
|
|
* Check that the stats merging accumulates rather than overwrites by
|
|
* putting some (made up) data there to begin with.
|
|
*/
|
|
stats.bytes = 123;
|
|
sec_stats_merge(tsdn, sec, &stats);
|
|
assert_zu_le(npages * PAGE + 123, stats.bytes, "");
|
|
}
|
|
|
|
TEST_BEGIN(test_stats_simple) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
|
|
enum {
|
|
NITERS = 100,
|
|
FLUSH_PAGES = 20,
|
|
};
|
|
|
|
bool deferred_work_generated = false;
|
|
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
|
|
/* max_bytes */ FLUSH_PAGES * PAGE);
|
|
|
|
edata_t *allocs[FLUSH_PAGES];
|
|
for (size_t i = 0; i < FLUSH_PAGES; i++) {
|
|
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_stats_pages(tsdn, &sec, 0);
|
|
}
|
|
|
|
/* Increase and decrease, without flushing. */
|
|
for (size_t i = 0; i < NITERS; i++) {
|
|
for (size_t j = 0; j < FLUSH_PAGES / 2; j++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[j],
|
|
&deferred_work_generated);
|
|
expect_stats_pages(tsdn, &sec, j + 1);
|
|
}
|
|
for (size_t j = 0; j < FLUSH_PAGES / 2; j++) {
|
|
allocs[j] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false,
|
|
/* frequent_reuse */ false,
|
|
&deferred_work_generated);
|
|
expect_stats_pages(tsdn, &sec, FLUSH_PAGES / 2 - j - 1);
|
|
}
|
|
}
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_stats_auto_flush) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
|
|
enum {
|
|
FLUSH_PAGES = 10,
|
|
};
|
|
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
|
|
/* max_bytes */ FLUSH_PAGES * PAGE);
|
|
|
|
edata_t *extra_alloc0;
|
|
edata_t *extra_alloc1;
|
|
edata_t *allocs[2 * FLUSH_PAGES];
|
|
|
|
bool deferred_work_generated = false;
|
|
|
|
extra_alloc0 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
|
|
/* guarded */ false, /* frequent_reuse */ false,
|
|
&deferred_work_generated);
|
|
extra_alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, /* zero */ false,
|
|
/* guarded */ false, /* frequent_reuse */ false,
|
|
&deferred_work_generated);
|
|
|
|
for (size_t i = 0; i < 2 * FLUSH_PAGES; i++) {
|
|
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
}
|
|
|
|
for (size_t i = 0; i < FLUSH_PAGES; i++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
|
|
}
|
|
pai_dalloc(tsdn, &sec.pai, extra_alloc0, &deferred_work_generated);
|
|
|
|
/* Flush the remaining pages; stats should still work. */
|
|
for (size_t i = 0; i < FLUSH_PAGES; i++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES + i],
|
|
&deferred_work_generated);
|
|
}
|
|
|
|
pai_dalloc(tsdn, &sec.pai, extra_alloc1, &deferred_work_generated);
|
|
|
|
expect_stats_pages(tsdn, &sec, ta.alloc_count + ta.alloc_batch_count
|
|
- ta.dalloc_count - ta.dalloc_batch_count);
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_stats_manual_flush) {
|
|
pai_test_allocator_t ta;
|
|
pai_test_allocator_init(&ta);
|
|
sec_t sec;
|
|
|
|
/* See the note above -- we can't use the real tsd. */
|
|
tsdn_t *tsdn = TSDN_NULL;
|
|
|
|
enum {
|
|
FLUSH_PAGES = 10,
|
|
};
|
|
|
|
test_sec_init(&sec, &ta.pai, /* nshards */ 1, /* max_alloc */ PAGE,
|
|
/* max_bytes */ FLUSH_PAGES * PAGE);
|
|
|
|
bool deferred_work_generated = false;
|
|
edata_t *allocs[FLUSH_PAGES];
|
|
for (size_t i = 0; i < FLUSH_PAGES; i++) {
|
|
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
|
|
/* zero */ false, /* guarded */ false, /* frequent_reuse */
|
|
false, &deferred_work_generated);
|
|
expect_stats_pages(tsdn, &sec, 0);
|
|
}
|
|
|
|
/* Dalloc the first half of the allocations. */
|
|
for (size_t i = 0; i < FLUSH_PAGES / 2; i++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[i], &deferred_work_generated);
|
|
expect_stats_pages(tsdn, &sec, i + 1);
|
|
}
|
|
|
|
sec_flush(tsdn, &sec);
|
|
expect_stats_pages(tsdn, &sec, 0);
|
|
|
|
/* Flush the remaining pages. */
|
|
for (size_t i = 0; i < FLUSH_PAGES / 2; i++) {
|
|
pai_dalloc(tsdn, &sec.pai, allocs[FLUSH_PAGES / 2 + i],
|
|
&deferred_work_generated);
|
|
expect_stats_pages(tsdn, &sec, i + 1);
|
|
}
|
|
sec_disable(tsdn, &sec);
|
|
expect_stats_pages(tsdn, &sec, 0);
|
|
}
|
|
TEST_END
|
|
|
|
int
|
|
main(void) {
|
|
return test(
|
|
test_reuse,
|
|
test_auto_flush,
|
|
test_disable,
|
|
test_flush,
|
|
test_max_alloc_respected,
|
|
test_expand_shrink_delegate,
|
|
test_nshards_0,
|
|
test_stats_simple,
|
|
test_stats_auto_flush,
|
|
test_stats_manual_flush);
|
|
}
|