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(¢ral, 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(¢ral, 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(¢ral, 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(¢ral, 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(¢ral, 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(¢ral, &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