123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- #include "test/jemalloc_test.h"
- #include "jemalloc/internal/hpa.h"
- #include "jemalloc/internal/nstime.h"
- #define SHARD_IND 111
- #define ALLOC_MAX (HUGEPAGE / 4)
- typedef struct test_data_s test_data_t;
- struct test_data_s {
- /*
- * Must be the first member -- we convert back and forth between the
- * test_data_t and the hpa_shard_t;
- */
- hpa_shard_t shard;
- hpa_central_t central;
- base_t *base;
- edata_cache_t shard_edata_cache;
- emap_t emap;
- };
- static hpa_shard_opts_t test_hpa_shard_opts_default = {
- /* slab_max_alloc */
- ALLOC_MAX,
- /* hugification threshold */
- HUGEPAGE,
- /* dirty_mult */
- FXP_INIT_PERCENT(25),
- /* deferral_allowed */
- false,
- /* hugify_delay_ms */
- 10 * 1000,
- };
- static hpa_shard_t *
- create_test_data(hpa_hooks_t *hooks, hpa_shard_opts_t *opts) {
- bool err;
- base_t *base = base_new(TSDN_NULL, /* ind */ SHARD_IND,
- &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
- assert_ptr_not_null(base, "");
- test_data_t *test_data = malloc(sizeof(test_data_t));
- assert_ptr_not_null(test_data, "");
- test_data->base = base;
- err = edata_cache_init(&test_data->shard_edata_cache, base);
- assert_false(err, "");
- err = emap_init(&test_data->emap, test_data->base, /* zeroed */ false);
- assert_false(err, "");
- err = hpa_central_init(&test_data->central, test_data->base, hooks);
- assert_false(err, "");
- err = hpa_shard_init(&test_data->shard, &test_data->central,
- &test_data->emap, test_data->base, &test_data->shard_edata_cache,
- SHARD_IND, opts);
- assert_false(err, "");
- return (hpa_shard_t *)test_data;
- }
- static void
- destroy_test_data(hpa_shard_t *shard) {
- test_data_t *test_data = (test_data_t *)shard;
- base_delete(TSDN_NULL, test_data->base);
- free(test_data);
- }
- TEST_BEGIN(test_alloc_max) {
- test_skip_if(!hpa_supported());
- hpa_shard_t *shard = create_test_data(&hpa_hooks_default,
- &test_hpa_shard_opts_default);
- tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
- edata_t *edata;
- /* Small max */
- bool deferred_work_generated = false;
- edata = pai_alloc(tsdn, &shard->pai, ALLOC_MAX, PAGE, false, false,
- false, &deferred_work_generated);
- expect_ptr_not_null(edata, "Allocation of small max failed");
- edata = pai_alloc(tsdn, &shard->pai, ALLOC_MAX + PAGE, PAGE, false,
- false, false, &deferred_work_generated);
- expect_ptr_null(edata, "Allocation of larger than small max succeeded");
- destroy_test_data(shard);
- }
- TEST_END
- typedef struct mem_contents_s mem_contents_t;
- struct mem_contents_s {
- uintptr_t my_addr;
- size_t size;
- edata_t *my_edata;
- rb_node(mem_contents_t) link;
- };
- static int
- mem_contents_cmp(const mem_contents_t *a, const mem_contents_t *b) {
- return (a->my_addr > b->my_addr) - (a->my_addr < b->my_addr);
- }
- typedef rb_tree(mem_contents_t) mem_tree_t;
- rb_gen(static, mem_tree_, mem_tree_t, mem_contents_t, link,
- mem_contents_cmp);
- static void
- node_assert_ordered(mem_contents_t *a, mem_contents_t *b) {
- assert_zu_lt(a->my_addr, a->my_addr + a->size, "Overflow");
- assert_zu_le(a->my_addr + a->size, b->my_addr, "");
- }
- static void
- node_check(mem_tree_t *tree, mem_contents_t *contents) {
- edata_t *edata = contents->my_edata;
- assert_ptr_eq(contents, (void *)contents->my_addr, "");
- assert_ptr_eq(contents, edata_base_get(edata), "");
- assert_zu_eq(contents->size, edata_size_get(edata), "");
- assert_ptr_eq(contents->my_edata, edata, "");
- mem_contents_t *next = mem_tree_next(tree, contents);
- if (next != NULL) {
- node_assert_ordered(contents, next);
- }
- mem_contents_t *prev = mem_tree_prev(tree, contents);
- if (prev != NULL) {
- node_assert_ordered(prev, contents);
- }
- }
- static void
- node_insert(mem_tree_t *tree, edata_t *edata, size_t npages) {
- mem_contents_t *contents = (mem_contents_t *)edata_base_get(edata);
- contents->my_addr = (uintptr_t)edata_base_get(edata);
- contents->size = edata_size_get(edata);
- contents->my_edata = edata;
- mem_tree_insert(tree, contents);
- node_check(tree, contents);
- }
- static void
- node_remove(mem_tree_t *tree, edata_t *edata) {
- mem_contents_t *contents = (mem_contents_t *)edata_base_get(edata);
- node_check(tree, contents);
- mem_tree_remove(tree, contents);
- }
- TEST_BEGIN(test_stress) {
- test_skip_if(!hpa_supported());
- hpa_shard_t *shard = create_test_data(&hpa_hooks_default,
- &test_hpa_shard_opts_default);
- tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
- const size_t nlive_edatas_max = 500;
- size_t nlive_edatas = 0;
- edata_t **live_edatas = calloc(nlive_edatas_max, sizeof(edata_t *));
- /*
- * Nothing special about this constant; we're only fixing it for
- * consistency across runs.
- */
- size_t prng_state = (size_t)0x76999ffb014df07c;
- mem_tree_t tree;
- mem_tree_new(&tree);
- bool deferred_work_generated = false;
- for (size_t i = 0; i < 100 * 1000; i++) {
- size_t operation = prng_range_zu(&prng_state, 2);
- if (operation == 0) {
- /* Alloc */
- if (nlive_edatas == nlive_edatas_max) {
- continue;
- }
- /*
- * We make sure to get an even balance of small and
- * large allocations.
- */
- size_t npages_min = 1;
- size_t npages_max = ALLOC_MAX / PAGE;
- size_t npages = npages_min + prng_range_zu(&prng_state,
- npages_max - npages_min);
- edata_t *edata = pai_alloc(tsdn, &shard->pai,
- npages * PAGE, PAGE, false, false, false,
- &deferred_work_generated);
- assert_ptr_not_null(edata,
- "Unexpected allocation failure");
- live_edatas[nlive_edatas] = edata;
- nlive_edatas++;
- node_insert(&tree, edata, npages);
- } else {
- /* Free. */
- if (nlive_edatas == 0) {
- continue;
- }
- size_t victim = prng_range_zu(&prng_state, nlive_edatas);
- edata_t *to_free = live_edatas[victim];
- live_edatas[victim] = live_edatas[nlive_edatas - 1];
- nlive_edatas--;
- node_remove(&tree, to_free);
- pai_dalloc(tsdn, &shard->pai, to_free,
- &deferred_work_generated);
- }
- }
- size_t ntreenodes = 0;
- for (mem_contents_t *contents = mem_tree_first(&tree); contents != NULL;
- contents = mem_tree_next(&tree, contents)) {
- ntreenodes++;
- node_check(&tree, contents);
- }
- expect_zu_eq(ntreenodes, nlive_edatas, "");
- /*
- * Test hpa_shard_destroy, which requires as a precondition that all its
- * extents have been deallocated.
- */
- for (size_t i = 0; i < nlive_edatas; i++) {
- edata_t *to_free = live_edatas[i];
- node_remove(&tree, to_free);
- pai_dalloc(tsdn, &shard->pai, to_free,
- &deferred_work_generated);
- }
- hpa_shard_destroy(tsdn, shard);
- free(live_edatas);
- destroy_test_data(shard);
- }
- TEST_END
- static void
- expect_contiguous(edata_t **edatas, size_t nedatas) {
- for (size_t i = 0; i < nedatas; i++) {
- size_t expected = (size_t)edata_base_get(edatas[0])
- + i * PAGE;
- expect_zu_eq(expected, (size_t)edata_base_get(edatas[i]),
- "Mismatch at index %zu", i);
- }
- }
- TEST_BEGIN(test_alloc_dalloc_batch) {
- test_skip_if(!hpa_supported());
- hpa_shard_t *shard = create_test_data(&hpa_hooks_default,
- &test_hpa_shard_opts_default);
- tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
- bool deferred_work_generated = false;
- enum {NALLOCS = 8};
- edata_t *allocs[NALLOCS];
- /*
- * Allocate a mix of ways; first half from regular alloc, second half
- * from alloc_batch.
- */
- for (size_t i = 0; i < NALLOCS / 2; i++) {
- allocs[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE,
- /* zero */ false, /* guarded */ false,
- /* frequent_reuse */ false, &deferred_work_generated);
- expect_ptr_not_null(allocs[i], "Unexpected alloc failure");
- }
- edata_list_active_t allocs_list;
- edata_list_active_init(&allocs_list);
- size_t nsuccess = pai_alloc_batch(tsdn, &shard->pai, PAGE, NALLOCS / 2,
- &allocs_list, &deferred_work_generated);
- expect_zu_eq(NALLOCS / 2, nsuccess, "Unexpected oom");
- for (size_t i = NALLOCS / 2; i < NALLOCS; i++) {
- allocs[i] = edata_list_active_first(&allocs_list);
- edata_list_active_remove(&allocs_list, allocs[i]);
- }
- /*
- * Should have allocated them contiguously, despite the differing
- * methods used.
- */
- void *orig_base = edata_base_get(allocs[0]);
- expect_contiguous(allocs, NALLOCS);
- /*
- * Batch dalloc the first half, individually deallocate the second half.
- */
- for (size_t i = 0; i < NALLOCS / 2; i++) {
- edata_list_active_append(&allocs_list, allocs[i]);
- }
- pai_dalloc_batch(tsdn, &shard->pai, &allocs_list,
- &deferred_work_generated);
- for (size_t i = NALLOCS / 2; i < NALLOCS; i++) {
- pai_dalloc(tsdn, &shard->pai, allocs[i],
- &deferred_work_generated);
- }
- /* Reallocate (individually), and ensure reuse and contiguity. */
- for (size_t i = 0; i < NALLOCS; i++) {
- allocs[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE,
- /* zero */ false, /* guarded */ false, /* frequent_reuse */
- false, &deferred_work_generated);
- expect_ptr_not_null(allocs[i], "Unexpected alloc failure.");
- }
- void *new_base = edata_base_get(allocs[0]);
- expect_ptr_eq(orig_base, new_base,
- "Failed to reuse the allocated memory.");
- expect_contiguous(allocs, NALLOCS);
- destroy_test_data(shard);
- }
- TEST_END
- static uintptr_t defer_bump_ptr = HUGEPAGE * 123;
- static void *
- defer_test_map(size_t size) {
- void *result = (void *)defer_bump_ptr;
- defer_bump_ptr += size;
- return result;
- }
- static void
- defer_test_unmap(void *ptr, size_t size) {
- (void)ptr;
- (void)size;
- }
- static bool defer_purge_called = false;
- static void
- defer_test_purge(void *ptr, size_t size) {
- (void)ptr;
- (void)size;
- defer_purge_called = true;
- }
- static bool defer_hugify_called = false;
- static void
- defer_test_hugify(void *ptr, size_t size) {
- defer_hugify_called = true;
- }
- static bool defer_dehugify_called = false;
- static void
- defer_test_dehugify(void *ptr, size_t size) {
- defer_dehugify_called = true;
- }
- static nstime_t defer_curtime;
- static void
- defer_test_curtime(nstime_t *r_time, bool first_reading) {
- *r_time = defer_curtime;
- }
- static uint64_t
- defer_test_ms_since(nstime_t *past_time) {
- return (nstime_ns(&defer_curtime) - nstime_ns(past_time)) / 1000 / 1000;
- }
- TEST_BEGIN(test_defer_time) {
- test_skip_if(!hpa_supported());
- hpa_hooks_t hooks;
- hooks.map = &defer_test_map;
- hooks.unmap = &defer_test_unmap;
- hooks.purge = &defer_test_purge;
- hooks.hugify = &defer_test_hugify;
- hooks.dehugify = &defer_test_dehugify;
- hooks.curtime = &defer_test_curtime;
- hooks.ms_since = &defer_test_ms_since;
- hpa_shard_opts_t opts = test_hpa_shard_opts_default;
- opts.deferral_allowed = true;
- hpa_shard_t *shard = create_test_data(&hooks, &opts);
- bool deferred_work_generated = false;
- nstime_init(&defer_curtime, 0);
- tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
- edata_t *edatas[HUGEPAGE_PAGES];
- for (int i = 0; i < (int)HUGEPAGE_PAGES; i++) {
- edatas[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, false,
- false, false, &deferred_work_generated);
- expect_ptr_not_null(edatas[i], "Unexpected null edata");
- }
- hpa_shard_do_deferred_work(tsdn, shard);
- expect_false(defer_hugify_called, "Hugified too early");
- /* Hugification delay is set to 10 seconds in options. */
- nstime_init2(&defer_curtime, 11, 0);
- hpa_shard_do_deferred_work(tsdn, shard);
- expect_true(defer_hugify_called, "Failed to hugify");
- defer_hugify_called = false;
- /* Purge. Recall that dirty_mult is .25. */
- for (int i = 0; i < (int)HUGEPAGE_PAGES / 2; i++) {
- pai_dalloc(tsdn, &shard->pai, edatas[i],
- &deferred_work_generated);
- }
- hpa_shard_do_deferred_work(tsdn, shard);
- expect_false(defer_hugify_called, "Hugified too early");
- expect_true(defer_dehugify_called, "Should have dehugified");
- expect_true(defer_purge_called, "Should have purged");
- defer_hugify_called = false;
- defer_dehugify_called = false;
- defer_purge_called = false;
- /*
- * Refill the page. We now meet the hugification threshold; we should
- * be marked for pending hugify.
- */
- for (int i = 0; i < (int)HUGEPAGE_PAGES / 2; i++) {
- edatas[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, false,
- false, false, &deferred_work_generated);
- expect_ptr_not_null(edatas[i], "Unexpected null edata");
- }
- /*
- * We would be ineligible for hugification, had we not already met the
- * threshold before dipping below it.
- */
- pai_dalloc(tsdn, &shard->pai, edatas[0],
- &deferred_work_generated);
- /* Wait for the threshold again. */
- nstime_init2(&defer_curtime, 22, 0);
- hpa_shard_do_deferred_work(tsdn, shard);
- expect_true(defer_hugify_called, "Hugified too early");
- expect_false(defer_dehugify_called, "Unexpected dehugify");
- expect_false(defer_purge_called, "Unexpected purge");
- destroy_test_data(shard);
- }
- TEST_END
- int
- main(void) {
- /*
- * These trigger unused-function warnings on CI runs, even if declared
- * with static inline.
- */
- (void)mem_tree_empty;
- (void)mem_tree_last;
- (void)mem_tree_search;
- (void)mem_tree_nsearch;
- (void)mem_tree_psearch;
- (void)mem_tree_iter;
- (void)mem_tree_reverse_iter;
- (void)mem_tree_destroy;
- return test_no_reentrancy(
- test_alloc_max,
- test_stress,
- test_alloc_dalloc_batch,
- test_defer_time);
- }
|