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 "test/hpa.h"
      4  1.1  christos 
      5  1.1  christos #include "jemalloc/internal/hpa.h"
      6  1.1  christos #include "jemalloc/internal/hpa_central.h"
      7  1.1  christos 
      8  1.1  christos #define HPA_TEST_ARENA_IND 125
      9  1.1  christos #define HPA_TEST_EDEN_SIZE (128 * HUGEPAGE)
     10  1.1  christos 
     11  1.1  christos static bool hpa_test_map_fail;
     12  1.1  christos static unsigned hpa_test_map_calls;
     13  1.1  christos static unsigned hpa_test_unmap_calls;
     14  1.1  christos static unsigned hpa_test_hugify_calls;
     15  1.1  christos static void *hpa_test_last_map;
     16  1.1  christos static size_t hpa_test_last_map_size;
     17  1.1  christos static void *hpa_test_last_unmap;
     18  1.1  christos static size_t hpa_test_last_unmap_size;
     19  1.1  christos static void *hpa_test_last_hugify;
     20  1.1  christos static size_t hpa_test_last_hugify_size;
     21  1.1  christos 
     22  1.1  christos static void
     23  1.1  christos hpa_test_hooks_reset(void) {
     24  1.1  christos 	hpa_test_map_fail = false;
     25  1.1  christos 	hpa_test_map_calls = 0;
     26  1.1  christos 	hpa_test_unmap_calls = 0;
     27  1.1  christos 	hpa_test_hugify_calls = 0;
     28  1.1  christos 	hpa_test_last_map = NULL;
     29  1.1  christos 	hpa_test_last_map_size = 0;
     30  1.1  christos 	hpa_test_last_unmap = NULL;
     31  1.1  christos 	hpa_test_last_unmap_size = 0;
     32  1.1  christos 	hpa_test_last_hugify = NULL;
     33  1.1  christos 	hpa_test_last_hugify_size = 0;
     34  1.1  christos }
     35  1.1  christos 
     36  1.1  christos static void *
     37  1.1  christos hpa_test_map(size_t size) {
     38  1.1  christos 	hpa_test_map_calls++;
     39  1.1  christos 	hpa_test_last_map_size = size;
     40  1.1  christos 	if (hpa_test_map_fail) {
     41  1.1  christos 		hpa_test_last_map = NULL;
     42  1.1  christos 		return NULL;
     43  1.1  christos 	}
     44  1.1  christos 	bool commit = true;
     45  1.1  christos 	void *ret = pages_map(NULL, size, HUGEPAGE, &commit);
     46  1.1  christos 	assert_true(commit, "HPA test mappings should be committed");
     47  1.1  christos 	hpa_test_last_map = ret;
     48  1.1  christos 	return ret;
     49  1.1  christos }
     50  1.1  christos 
     51  1.1  christos static void
     52  1.1  christos hpa_test_unmap(void *ptr, size_t size) {
     53  1.1  christos 	hpa_test_unmap_calls++;
     54  1.1  christos 	hpa_test_last_unmap = ptr;
     55  1.1  christos 	hpa_test_last_unmap_size = size;
     56  1.1  christos 	pages_unmap(ptr, size);
     57  1.1  christos }
     58  1.1  christos 
     59  1.1  christos static void
     60  1.1  christos hpa_test_purge(void *ptr, size_t size) {
     61  1.1  christos }
     62  1.1  christos 
     63  1.1  christos static bool
     64  1.1  christos hpa_test_hugify(void *ptr, size_t size, bool sync) {
     65  1.1  christos 	hpa_test_hugify_calls++;
     66  1.1  christos 	hpa_test_last_hugify = ptr;
     67  1.1  christos 	hpa_test_last_hugify_size = size;
     68  1.1  christos 	return false;
     69  1.1  christos }
     70  1.1  christos 
     71  1.1  christos static void
     72  1.1  christos hpa_test_dehugify(void *ptr, size_t size) {
     73  1.1  christos }
     74  1.1  christos 
     75  1.1  christos static void
     76  1.1  christos hpa_test_curtime(nstime_t *r_time, bool first_reading) {
     77  1.1  christos 	nstime_init(r_time, 0);
     78  1.1  christos }
     79  1.1  christos 
     80  1.1  christos static uint64_t
     81  1.1  christos hpa_test_ms_since(nstime_t *r_time) {
     82  1.1  christos 	return 0;
     83  1.1  christos }
     84  1.1  christos 
     85  1.1  christos static bool
     86  1.1  christos hpa_test_vectorized_purge(void *vec, size_t vlen, size_t nbytes) {
     87  1.1  christos 	return true;
     88  1.1  christos }
     89  1.1  christos 
     90  1.1  christos static hpa_hooks_t hpa_test_hooks = {
     91  1.1  christos 	hpa_test_map,
     92  1.1  christos 	hpa_test_unmap,
     93  1.1  christos 	hpa_test_purge,
     94  1.1  christos 	hpa_test_hugify,
     95  1.1  christos 	hpa_test_dehugify,
     96  1.1  christos 	hpa_test_curtime,
     97  1.1  christos 	hpa_test_ms_since,
     98  1.1  christos 	hpa_test_vectorized_purge
     99  1.1  christos };
    100  1.1  christos 
    101  1.1  christos static bool hpa_base_fail_after_new;
    102  1.1  christos static unsigned hpa_base_alloc_calls;
    103  1.1  christos 
    104  1.1  christos static void
    105  1.1  christos hpa_base_hooks_reset(void) {
    106  1.1  christos 	hpa_base_fail_after_new = false;
    107  1.1  christos 	hpa_base_alloc_calls = 0;
    108  1.1  christos }
    109  1.1  christos 
    110  1.1  christos static void *
    111  1.1  christos hpa_base_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size,
    112  1.1  christos     size_t alignment, bool *zero, bool *commit, unsigned arena_ind) {
    113  1.1  christos 	if (hpa_base_fail_after_new && hpa_base_alloc_calls > 0) {
    114  1.1  christos 		hpa_base_alloc_calls++;
    115  1.1  christos 		return NULL;
    116  1.1  christos 	}
    117  1.1  christos 	hpa_base_alloc_calls++;
    118  1.1  christos 	return pages_map(new_addr, size, alignment, commit);
    119  1.1  christos }
    120  1.1  christos 
    121  1.1  christos static bool
    122  1.1  christos hpa_base_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size,
    123  1.1  christos     bool committed, unsigned arena_ind) {
    124  1.1  christos 	pages_unmap(addr, size);
    125  1.1  christos 	return false;
    126  1.1  christos }
    127  1.1  christos 
    128  1.1  christos static void
    129  1.1  christos hpa_base_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size,
    130  1.1  christos     bool committed, unsigned arena_ind) {
    131  1.1  christos 	pages_unmap(addr, size);
    132  1.1  christos }
    133  1.1  christos 
    134  1.1  christos static extent_hooks_t hpa_base_hooks = {
    135  1.1  christos 	hpa_base_alloc,
    136  1.1  christos 	hpa_base_dalloc,
    137  1.1  christos 	hpa_base_destroy,
    138  1.1  christos 	NULL, /* commit */
    139  1.1  christos 	NULL, /* decommit */
    140  1.1  christos 	NULL, /* purge_lazy */
    141  1.1  christos 	NULL, /* purge_forced */
    142  1.1  christos 	NULL, /* split */
    143  1.1  christos 	NULL  /* merge */
    144  1.1  christos };
    145  1.1  christos 
    146  1.1  christos static hpdata_t *
    147  1.1  christos hpa_central_extract_with_lock(hpa_central_t *central, malloc_mutex_t *mtx,
    148  1.1  christos     uint64_t age, bool hugify_eager, bool *oom) {
    149  1.1  christos 	tsdn_t *tsdn = tsdn_fetch();
    150  1.1  christos 	malloc_mutex_lock(tsdn, mtx);
    151  1.1  christos 	hpdata_t *ret = hpa_central_extract(tsdn, central, PAGE, age,
    152  1.1  christos 	    hugify_eager, oom);
    153  1.1  christos 	malloc_mutex_unlock(tsdn, mtx);
    154  1.1  christos 	return ret;
    155  1.1  christos }
    156  1.1  christos 
    157  1.1  christos static void
    158  1.1  christos hpa_test_shard_grow_mtx_init(malloc_mutex_t *mtx) {
    159  1.1  christos 	assert_false(malloc_mutex_init(mtx, "hpa_test_shard_grow",
    160  1.1  christos 	    WITNESS_RANK_HPA_SHARD_GROW, malloc_mutex_rank_exclusive),
    161  1.1  christos 	    "Unexpected mutex initialization failure");
    162  1.1  christos }
    163  1.1  christos 
    164  1.1  christos TEST_BEGIN(test_hpa_central_extract_eden) {
    165  1.1  christos 	test_skip_if(!hpa_supported() || hpa_hugepage_size_exceeds_limit());
    166  1.1  christos 	hpa_test_hooks_reset();
    167  1.1  christos 
    168  1.1  christos 	tsdn_t *tsdn = tsdn_fetch();
    169  1.1  christos 	base_t *base = base_new(tsdn, HPA_TEST_ARENA_IND,
    170  1.1  christos 	    &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
    171  1.1  christos 	assert_ptr_not_null(base, "Unexpected base_new failure");
    172  1.1  christos 
    173  1.1  christos 	hpa_central_t central;
    174  1.1  christos 	assert_false(hpa_central_init(&central, base, &hpa_test_hooks),
    175  1.1  christos 	    "Unexpected hpa_central_init failure");
    176  1.1  christos 	malloc_mutex_t shard_grow_mtx;
    177  1.1  christos 	hpa_test_shard_grow_mtx_init(&shard_grow_mtx);
    178  1.1  christos 
    179  1.1  christos 	void *eden = NULL;
    180  1.1  christos 	for (unsigned i = 0; i < 128; i++) {
    181  1.1  christos 		bool oom = true;
    182  1.1  christos 		hpdata_t *ps = hpa_central_extract_with_lock(&central,
    183  1.1  christos 		    &shard_grow_mtx, 1000 + i, /* hugify_eager */ true, &oom);
    184  1.1  christos 		expect_false(oom, "Unexpected HPA central OOM");
    185  1.1  christos 		expect_ptr_not_null(ps, "Unexpected HPA central extraction failure");
    186  1.1  christos 		if (i == 0) {
    187  1.1  christos 			eden = hpa_test_last_map;
    188  1.1  christos 			expect_u_eq(1, hpa_test_map_calls,
    189  1.1  christos 			    "First extraction should map eden");
    190  1.1  christos 			expect_zu_eq(HPA_TEST_EDEN_SIZE, hpa_test_last_map_size,
    191  1.1  christos 			    "Unexpected eden mapping size");
    192  1.1  christos 			expect_u_eq(1, hpa_test_hugify_calls,
    193  1.1  christos 			    "Eager extraction should hugify the whole eden");
    194  1.1  christos 			expect_ptr_eq(eden, hpa_test_last_hugify,
    195  1.1  christos 			    "Hugify should apply to eden");
    196  1.1  christos 			expect_zu_eq(HPA_TEST_EDEN_SIZE,
    197  1.1  christos 			    hpa_test_last_hugify_size,
    198  1.1  christos 			    "Hugify should cover the whole eden");
    199  1.1  christos 		}
    200  1.1  christos 		expect_ptr_eq((void *)((byte_t *)eden + i * HUGEPAGE),
    201  1.1  christos 		    hpdata_addr_get(ps), "Unexpected extracted pageslab addr");
    202  1.1  christos 		expect_u64_eq(1000 + i, hpdata_age_get(ps),
    203  1.1  christos 		    "Unexpected hpdata age");
    204  1.1  christos 		expect_true(hpdata_huge_get(ps),
    205  1.1  christos 		    "Eager extraction should mark hpdata huge");
    206  1.1  christos 	}
    207  1.1  christos 
    208  1.1  christos 	expect_ptr_null(central.eden, "Exact final extraction should empty eden");
    209  1.1  christos 	expect_zu_eq(0, central.eden_len,
    210  1.1  christos 	    "Exact final extraction should clear eden length");
    211  1.1  christos 	expect_u_eq(1, hpa_test_map_calls,
    212  1.1  christos 	    "All pageslabs should come from one eden mapping");
    213  1.1  christos 	expect_u_eq(0, hpa_test_unmap_calls,
    214  1.1  christos 	    "Successful extraction should not unmap eden");
    215  1.1  christos 
    216  1.1  christos 	pages_unmap(eden, HPA_TEST_EDEN_SIZE);
    217  1.1  christos 	base_delete(tsdn, base);
    218  1.1  christos }
    219  1.1  christos TEST_END
    220  1.1  christos 
    221  1.1  christos TEST_BEGIN(test_hpa_central_failure_paths) {
    222  1.1  christos 	test_skip_if(!hpa_supported() || hpa_hugepage_size_exceeds_limit());
    223  1.1  christos 	hpa_test_hooks_reset();
    224  1.1  christos 
    225  1.1  christos 	tsdn_t *tsdn = tsdn_fetch();
    226  1.1  christos 	base_t *base = base_new(tsdn, HPA_TEST_ARENA_IND + 1,
    227  1.1  christos 	    &ehooks_default_extent_hooks, /* metadata_use_hooks */ true);
    228  1.1  christos 	assert_ptr_not_null(base, "Unexpected base_new failure");
    229  1.1  christos 	hpa_central_t central;
    230  1.1  christos 	assert_false(hpa_central_init(&central, base, &hpa_test_hooks),
    231  1.1  christos 	    "Unexpected hpa_central_init failure");
    232  1.1  christos 	malloc_mutex_t shard_grow_mtx;
    233  1.1  christos 	hpa_test_shard_grow_mtx_init(&shard_grow_mtx);
    234  1.1  christos 
    235  1.1  christos 	hpa_test_map_fail = true;
    236  1.1  christos 	bool oom = false;
    237  1.1  christos 	hpdata_t *ps = hpa_central_extract_with_lock(&central,
    238  1.1  christos 	    &shard_grow_mtx, 1, /* hugify_eager */ false, &oom);
    239  1.1  christos 	expect_ptr_null(ps, "Map failure should not return hpdata");
    240  1.1  christos 	expect_true(oom, "Map failure should report OOM");
    241  1.1  christos 	expect_u_eq(1, hpa_test_map_calls, "Expected one map attempt");
    242  1.1  christos 	expect_u_eq(0, hpa_test_unmap_calls,
    243  1.1  christos 	    "Map failure should not call unmap");
    244  1.1  christos 	base_delete(tsdn, base);
    245  1.1  christos 
    246  1.1  christos 	hpa_base_hooks_reset();
    247  1.1  christos 	hpa_test_hooks_reset();
    248  1.1  christos 	base = base_new(tsdn, HPA_TEST_ARENA_IND + 2, &hpa_base_hooks,
    249  1.1  christos 	    /* metadata_use_hooks */ true);
    250  1.1  christos 	assert_ptr_not_null(base, "Unexpected base_new failure");
    251  1.1  christos 	hpa_base_fail_after_new = true;
    252  1.1  christos 	while (base_alloc(tsdn, base, sizeof(hpdata_t), CACHELINE) != NULL) {
    253  1.1  christos 	}
    254  1.1  christos 
    255  1.1  christos 	assert_false(hpa_central_init(&central, base, &hpa_test_hooks),
    256  1.1  christos 	    "Unexpected hpa_central_init failure");
    257  1.1  christos 	malloc_mutex_t shard_grow_mtx2;
    258  1.1  christos 	hpa_test_shard_grow_mtx_init(&shard_grow_mtx2);
    259  1.1  christos 	oom = false;
    260  1.1  christos 	ps = hpa_central_extract_with_lock(&central, &shard_grow_mtx2, 2,
    261  1.1  christos 	    /* hugify_eager */ false, &oom);
    262  1.1  christos 	expect_ptr_null(ps, "Metadata OOM should not return hpdata");
    263  1.1  christos 	expect_true(oom, "Metadata allocation failure should report OOM");
    264  1.1  christos 	expect_u_eq(1, hpa_test_map_calls,
    265  1.1  christos 	    "Metadata OOM should happen after mapping eden once");
    266  1.1  christos 	expect_u_eq(1, hpa_test_unmap_calls,
    267  1.1  christos 	    "Metadata OOM should unmap the freshly mapped eden");
    268  1.1  christos 	expect_ptr_eq(hpa_test_last_map, hpa_test_last_unmap,
    269  1.1  christos 	    "Metadata OOM should unmap the eden it just mapped");
    270  1.1  christos 	expect_zu_eq(HPA_TEST_EDEN_SIZE, hpa_test_last_unmap_size,
    271  1.1  christos 	    "Metadata OOM should unmap the full eden");
    272  1.1  christos 	base_delete(tsdn, base);
    273  1.1  christos }
    274  1.1  christos TEST_END
    275  1.1  christos 
    276  1.1  christos int
    277  1.1  christos main(void) {
    278  1.1  christos 	return test_no_reentrancy(test_hpa_central_extract_eden,
    279  1.1  christos 	    test_hpa_central_failure_paths);
    280  1.1  christos }
    281