#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;
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;
opts.bytes_after_flush = max_bytes / 2;
opts.batch_fill_extra = 4;
base_t *base = base_new(TSDN_NULL, 123,
&ehooks_default_extent_hooks, true);
bool err = sec_init(TSDN_NULL, sec, base, fallback, &opts);
assert_false(err, "Unexpected initialization failure");
assert_u_ge(sec->npsizes, 0, "Zero size classes allowed for caching");
}
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, 0,
(void *)(ta->next_ptr & ~(alignment - 1)), size,
false,
0, 1, extent_state_active, zero,
true, 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, 0,
(void *)ta->next_ptr, size,
false, 0, 1,
extent_state_active, false, true,
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;
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;
tsdn_t *tsdn = TSDN_NULL;
enum { NALLOCS = 11 };
edata_t *one_page[NALLOCS];
edata_t *two_page[NALLOCS];
bool deferred_work_generated = false;
test_sec_init(&sec, &ta.pai, 1, 2 * PAGE,
2 * (NALLOCS * PAGE + NALLOCS * 2 * PAGE));
for (int i = 0; i < NALLOCS; i++) {
one_page[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
false, false,
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,
false, false,
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");
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");
for (int i = 0; i < NALLOCS; i++) {
edata_t *alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
false, false,
false, &deferred_work_generated);
edata_t *alloc2 = pai_alloc(tsdn, &sec.pai, 2 * PAGE, PAGE,
false, false,
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;
tsdn_t *tsdn = TSDN_NULL;
enum { NALLOCS = 10 };
edata_t *extra_alloc;
edata_t *allocs[NALLOCS];
bool deferred_work_generated = false;
test_sec_init(&sec, &ta.pai, 1, PAGE,
NALLOCS * PAGE);
for (int i = 0; i < NALLOCS; i++) {
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
false, false,
false, &deferred_work_generated);
expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
}
extra_alloc = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, false,
false, 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");
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");
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
static void
do_disable_flush_test(bool is_disable) {
pai_test_allocator_t ta;
pai_test_allocator_init(&ta);
sec_t sec;
tsdn_t *tsdn = TSDN_NULL;
enum { NALLOCS = 11 };
edata_t *allocs[NALLOCS];
bool deferred_work_generated = false;
test_sec_init(&sec, &ta.pai, 1, PAGE,
NALLOCS * PAGE);
for (int i = 0; i < NALLOCS; i++) {
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
false, false,
false, &deferred_work_generated);
expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
}
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;
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( true);
}
TEST_END
TEST_BEGIN(test_flush) {
do_disable_flush_test( false);
}
TEST_END
TEST_BEGIN(test_max_alloc_respected) {
pai_test_allocator_t ta;
pai_test_allocator_init(&ta);
sec_t sec;
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, 1, max_alloc,
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, false, false,
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) {
pai_test_allocator_t ta;
pai_test_allocator_init(&ta);
sec_t sec;
tsdn_t *tsdn = TSDN_NULL;
bool deferred_work_generated = false;
test_sec_init(&sec, &ta.pai, 1, 10 * PAGE,
1000 * PAGE);
edata_t *edata = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
false, false, false,
&deferred_work_generated);
expect_ptr_not_null(edata, "Unexpected alloc failure");
bool err = pai_expand(tsdn, &sec.pai, edata, PAGE, 4 * PAGE,
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,
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;
tsdn_t *tsdn = TSDN_NULL;
base_t *base = base_new(TSDN_NULL, 123,
&ehooks_default_extent_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,
false, false, false,
&deferred_work_generated);
pai_dalloc(tsdn, &sec.pai, edata, &deferred_work_generated);
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;
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;
tsdn_t *tsdn = TSDN_NULL;
enum {
NITERS = 100,
FLUSH_PAGES = 20,
};
bool deferred_work_generated = false;
test_sec_init(&sec, &ta.pai, 1, PAGE,
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,
false, false,
false, &deferred_work_generated);
expect_stats_pages(tsdn, &sec, 0);
}
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,
false, false,
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;
tsdn_t *tsdn = TSDN_NULL;
enum {
FLUSH_PAGES = 10,
};
test_sec_init(&sec, &ta.pai, 1, PAGE,
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, false,
false, false,
&deferred_work_generated);
extra_alloc1 = pai_alloc(tsdn, &sec.pai, PAGE, PAGE, false,
false, false,
&deferred_work_generated);
for (size_t i = 0; i < 2 * FLUSH_PAGES; i++) {
allocs[i] = pai_alloc(tsdn, &sec.pai, PAGE, PAGE,
false, false,
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);
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;
tsdn_t *tsdn = TSDN_NULL;
enum {
FLUSH_PAGES = 10,
};
test_sec_init(&sec, &ta.pai, 1, PAGE,
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,
false, false,
false, &deferred_work_generated);
expect_stats_pages(tsdn, &sec, 0);
}
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);
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);
}