1 1.1 christos #include "test/jemalloc_test.h" 2 1.1 christos 3 1.1 christos static void * 4 1.1 christos pinned_extent_alloc(extent_hooks_t *extent_hooks, void *new_addr, 5 1.1 christos size_t size, size_t alignment, bool *zero, bool *commit, 6 1.1 christos unsigned arena_ind) { 7 1.1 christos void *ret = ehooks_default_extent_hooks.alloc( 8 1.1 christos (extent_hooks_t *)&ehooks_default_extent_hooks, 9 1.1 christos new_addr, size, alignment, zero, commit, arena_ind); 10 1.1 christos if (ret == NULL) { 11 1.1 christos return NULL; 12 1.1 christos } 13 1.1 christos if (!*commit) { 14 1.1 christos if (ehooks_default_extent_hooks.commit != NULL && 15 1.1 christos ehooks_default_extent_hooks.commit( 16 1.1 christos (extent_hooks_t *)&ehooks_default_extent_hooks, ret, size, 17 1.1 christos 0, size, arena_ind)) { 18 1.1 christos ehooks_default_extent_hooks.dalloc( 19 1.1 christos (extent_hooks_t *)&ehooks_default_extent_hooks, ret, 20 1.1 christos size, *commit, arena_ind); 21 1.1 christos return NULL; 22 1.1 christos } 23 1.1 christos *commit = true; 24 1.1 christos } 25 1.1 christos return (void *)((uintptr_t)ret | EXTENT_ALLOC_FLAG_PINNED); 26 1.1 christos } 27 1.1 christos 28 1.1 christos static unsigned pinned_split_calls; 29 1.1 christos static unsigned pinned_destroy_calls; 30 1.1 christos static size_t pinned_destroy_bytes; 31 1.1 christos 32 1.1 christos static bool 33 1.1 christos pinned_extent_split(extent_hooks_t *extent_hooks, void *addr, size_t size, 34 1.1 christos size_t size_a, size_t size_b, bool committed, unsigned arena_ind) { 35 1.1 christos pinned_split_calls++; 36 1.1 christos return ehooks_default_extent_hooks.split( 37 1.1 christos (extent_hooks_t *)&ehooks_default_extent_hooks, addr, size, size_a, 38 1.1 christos size_b, committed, arena_ind); 39 1.1 christos } 40 1.1 christos 41 1.1 christos static bool 42 1.1 christos pinned_extent_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a, 43 1.1 christos void *addr_b, size_t size_b, bool committed, unsigned arena_ind) { 44 1.1 christos return ehooks_default_extent_hooks.merge( 45 1.1 christos (extent_hooks_t *)&ehooks_default_extent_hooks, addr_a, size_a, 46 1.1 christos addr_b, size_b, committed, arena_ind); 47 1.1 christos } 48 1.1 christos 49 1.1 christos static void 50 1.1 christos pinned_extent_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size, 51 1.1 christos bool committed, unsigned arena_ind) { 52 1.1 christos pinned_destroy_calls++; 53 1.1 christos pinned_destroy_bytes += size; 54 1.1 christos ehooks_default_extent_hooks.destroy( 55 1.1 christos (extent_hooks_t *)&ehooks_default_extent_hooks, addr, size, 56 1.1 christos committed, arena_ind); 57 1.1 christos } 58 1.1 christos 59 1.1 christos static void 60 1.1 christos pinned_hooks_reset(void) { 61 1.1 christos pinned_split_calls = 0; 62 1.1 christos pinned_destroy_calls = 0; 63 1.1 christos pinned_destroy_bytes = 0; 64 1.1 christos } 65 1.1 christos 66 1.1 christos static extent_hooks_t pinned_hooks = { 67 1.1 christos pinned_extent_alloc, 68 1.1 christos NULL, /* dalloc force retain */ 69 1.1 christos pinned_extent_destroy, 70 1.1 christos NULL, /* commit */ 71 1.1 christos NULL, /* decommit */ 72 1.1 christos NULL, /* purge_lazy */ 73 1.1 christos NULL, /* purge_forced */ 74 1.1 christos pinned_extent_split, 75 1.1 christos pinned_extent_merge 76 1.1 christos }; 77 1.1 christos 78 1.1 christos static size_t 79 1.1 christos get_arena_mapped(unsigned arena_ind) { 80 1.1 christos uint64_t epoch = 1; 81 1.1 christos size_t epoch_sz = sizeof(epoch); 82 1.1 christos expect_d_eq(0, mallctl("epoch", &epoch, &epoch_sz, &epoch, 83 1.1 christos sizeof(epoch)), "epoch failed"); 84 1.1 christos size_t mapped; 85 1.1 christos size_t mapped_sz = sizeof(mapped); 86 1.1 christos char buf[64]; 87 1.1 christos snprintf(buf, sizeof(buf), "stats.arenas.%u.mapped", arena_ind); 88 1.1 christos expect_d_eq(0, mallctl(buf, &mapped, &mapped_sz, NULL, 0), 89 1.1 christos "stats.arenas.<i>.mapped read failed"); 90 1.1 christos return mapped; 91 1.1 christos } 92 1.1 christos 93 1.1 christos /* 94 1.1 christos * Non-dependent emap lookup: returns the edata for addr, or NULL if the 95 1.1 christos * rtree leaf does not exist (safe for addresses that jemalloc may never 96 1.1 christos * have mapped, e.g. after arena destroy). 97 1.1 christos */ 98 1.1 christos static edata_t * 99 1.1 christos emap_edata_try_lookup(const void *ptr) { 100 1.1 christos emap_full_alloc_ctx_t ctx; 101 1.1 christos bool err = emap_full_alloc_ctx_try_lookup(TSDN_NULL, 102 1.1 christos &arena_emap_global, ptr, &ctx); 103 1.1 christos if (err) { 104 1.1 christos return NULL; 105 1.1 christos } 106 1.1 christos return ctx.edata; 107 1.1 christos } 108 1.1 christos 109 1.1 christos /* 110 1.1 christos * Find the edata covering addr by walking the emap from addr in PAGE 111 1.1 christos * strides. Returns NULL if no covering edata is found within max_bytes. 112 1.1 christos */ 113 1.1 christos static edata_t * 114 1.1 christos find_covering_edata(const void *addr, size_t max_bytes) { 115 1.1 christos uintptr_t a = (uintptr_t)addr; 116 1.1 christos edata_t *back = NULL; 117 1.1 christos for (size_t off = 0; off <= max_bytes; off += PAGE) { 118 1.1 christos back = emap_edata_try_lookup((void *)(a - off)); 119 1.1 christos if (back != NULL) { 120 1.1 christos break; 121 1.1 christos } 122 1.1 christos } 123 1.1 christos if (back == NULL) { 124 1.1 christos return NULL; 125 1.1 christos } 126 1.1 christos edata_t *fwd = NULL; 127 1.1 christos for (size_t off = 0; off <= max_bytes; off += PAGE) { 128 1.1 christos fwd = emap_edata_try_lookup((void *)(a + off)); 129 1.1 christos if (fwd != NULL) { 130 1.1 christos break; 131 1.1 christos } 132 1.1 christos } 133 1.1 christos return (back == fwd) ? back : NULL; 134 1.1 christos } 135 1.1 christos 136 1.1 christos TEST_BEGIN(test_pinned_stats) { 137 1.1 christos test_skip_if(!config_stats); 138 1.1 christos pinned_hooks_reset(); 139 1.1 christos 140 1.1 christos unsigned arena_ind; 141 1.1 christos size_t sz = sizeof(arena_ind); 142 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 143 1.1 christos 144 1.1 christos /* Create arena with pinned hooks. */ 145 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 146 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 147 1.1 christos "arena creation failed"); 148 1.1 christos 149 1.1 christos /* Allocate and free to populate ecache_pinned. */ 150 1.1 christos void *p = mallocx(PAGE * 4, MALLOCX_ARENA(arena_ind) 151 1.1 christos | MALLOCX_TCACHE_NONE); 152 1.1 christos expect_ptr_not_null(p, "alloc failed"); 153 1.1 christos dallocx(p, MALLOCX_TCACHE_NONE); 154 1.1 christos 155 1.1 christos /* Refresh stats. */ 156 1.1 christos uint64_t epoch = 1; 157 1.1 christos sz = sizeof(epoch); 158 1.1 christos expect_d_eq(0, mallctl("epoch", &epoch, &sz, &epoch, sizeof(epoch)), 159 1.1 christos "epoch failed"); 160 1.1 christos 161 1.1 christos /* Read total pinned stat. */ 162 1.1 christos char buf[128]; 163 1.1 christos size_t pinned_total; 164 1.1 christos sz = sizeof(pinned_total); 165 1.1 christos snprintf(buf, sizeof(buf), "stats.arenas.%u.pinned", arena_ind); 166 1.1 christos expect_d_eq(0, mallctl(buf, &pinned_total, &sz, NULL, 0), 167 1.1 christos "stats.arenas.<i>.pinned read failed"); 168 1.1 christos expect_zu_gt(pinned_total, 0, 169 1.1 christos "pinned total should be > 0 after free to pinned arena"); 170 1.1 christos 171 1.1 christos /* Pinned bytes are part of stats.mapped (unlike retained). */ 172 1.1 christos expect_zu_ge(get_arena_mapped(arena_ind), pinned_total, 173 1.1 christos "stats.mapped should include pinned bytes"); 174 1.1 christos 175 1.1 christos /* Destroy the arena. */ 176 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 177 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 178 1.1 christos "arena destroy failed"); 179 1.1 christos } 180 1.1 christos TEST_END 181 1.1 christos 182 1.1 christos TEST_BEGIN(test_pinned_shrink) { 183 1.1 christos test_skip_if(ehooks_default_split_impl()); 184 1.1 christos pinned_hooks_reset(); 185 1.1 christos 186 1.1 christos unsigned arena_ind; 187 1.1 christos size_t sz = sizeof(arena_ind); 188 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 189 1.1 christos 190 1.1 christos /* Create arena with pinned hooks. */ 191 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 192 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 193 1.1 christos "arena creation failed"); 194 1.1 christos 195 1.1 christos int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; 196 1.1 christos void *ptr = mallocx(SC_LARGE_MINCLASS + PAGE, flags); 197 1.1 christos expect_ptr_not_null(ptr, "alloc failed"); 198 1.1 christos unsigned split_calls_before = pinned_split_calls; 199 1.1 christos void *shrunk = rallocx(ptr, SC_LARGE_MINCLASS, flags); 200 1.1 christos expect_ptr_not_null(shrunk, "shrink failed"); 201 1.1 christos expect_u_gt(pinned_split_calls, split_calls_before, 202 1.1 christos "shrink should invoke the split hook"); 203 1.1 christos dallocx(shrunk, MALLOCX_TCACHE_NONE); 204 1.1 christos 205 1.1 christos char buf[64]; 206 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 207 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 208 1.1 christos "arena destroy failed"); 209 1.1 christos } 210 1.1 christos TEST_END 211 1.1 christos 212 1.1 christos TEST_BEGIN(test_pinned_remnant_lock) { 213 1.1 christos test_skip_if(!opt_retain); 214 1.1 christos pinned_hooks_reset(); 215 1.1 christos unsigned arena_ind; 216 1.1 christos size_t sz = sizeof(arena_ind); 217 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 218 1.1 christos 219 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 220 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 221 1.1 christos "arena creation failed"); 222 1.1 christos 223 1.1 christos int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; 224 1.1 christos 225 1.1 christos void *p1 = mallocx(SC_LARGE_MINCLASS, flags); 226 1.1 christos expect_ptr_not_null(p1, "first alloc failed"); 227 1.1 christos 228 1.1 christos tsd_t *tsd = tsd_fetch(); 229 1.1 christos arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); 230 1.1 christos expect_ptr_not_null(arena, "arena_get failed"); 231 1.1 christos expect_zu_gt(ecache_npages_get(&arena->pa_shard.pac.ecache_pinned), 232 1.1 christos 0, "grow remnant should be cached in ecache_pinned"); 233 1.1 christos 234 1.1 christos dallocx(p1, MALLOCX_TCACHE_NONE); 235 1.1 christos 236 1.1 christos char buf[64]; 237 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 238 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 239 1.1 christos "arena destroy failed"); 240 1.1 christos } 241 1.1 christos TEST_END 242 1.1 christos 243 1.1 christos TEST_BEGIN(test_pinned_reuse) { 244 1.1 christos pinned_hooks_reset(); 245 1.1 christos unsigned arena_ind; 246 1.1 christos size_t sz = sizeof(arena_ind); 247 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 248 1.1 christos 249 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 250 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 251 1.1 christos "arena creation failed"); 252 1.1 christos 253 1.1 christos int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; 254 1.1 christos 255 1.1 christos void *p1 = mallocx(PAGE * 2, flags); 256 1.1 christos expect_ptr_not_null(p1, "first alloc failed"); 257 1.1 christos dallocx(p1, MALLOCX_TCACHE_NONE); 258 1.1 christos 259 1.1 christos void *p2 = mallocx(PAGE * 2, flags); 260 1.1 christos expect_ptr_not_null(p2, "reuse alloc failed"); 261 1.1 christos expect_ptr_eq(p1, p2, 262 1.1 christos "pinned extent should be reused at the same address"); 263 1.1 christos dallocx(p2, MALLOCX_TCACHE_NONE); 264 1.1 christos 265 1.1 christos /* Destroy the arena. */ 266 1.1 christos char buf[64]; 267 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 268 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 269 1.1 christos "arena destroy failed"); 270 1.1 christos } 271 1.1 christos TEST_END 272 1.1 christos 273 1.1 christos TEST_BEGIN(test_pinned_realloc) { 274 1.1 christos test_skip_if(ehooks_default_split_impl()); 275 1.1 christos pinned_hooks_reset(); 276 1.1 christos unsigned arena_ind; 277 1.1 christos size_t sz = sizeof(arena_ind); 278 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 279 1.1 christos 280 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 281 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 282 1.1 christos "arena creation failed"); 283 1.1 christos 284 1.1 christos int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; 285 1.1 christos 286 1.1 christos /* 287 1.1 christos * Reallocs within the large-class range shrink/grow in place; the 288 1.1 christos * pointer stays at the original address rather than moving to a 289 1.1 christos * fresh extent (important since pinned memory is finite). All sizes 290 1.1 christos * are multiples of SC_LARGE_MINCLASS so they bypass the slab path 291 1.1 christos * regardless of LG_PAGE / SC_LG_NGROUP. 292 1.1 christos */ 293 1.1 christos void *p = mallocx(SC_LARGE_MINCLASS * 7, flags); 294 1.1 christos expect_ptr_not_null(p, "initial alloc failed"); 295 1.1 christos void *original = p; 296 1.1 christos 297 1.1 christos p = rallocx(p, SC_LARGE_MINCLASS * 3, flags); 298 1.1 christos expect_ptr_not_null(p, "shrink failed"); 299 1.1 christos expect_ptr_eq(p, original, "shrink should preserve address"); 300 1.1 christos 301 1.1 christos p = rallocx(p, SC_LARGE_MINCLASS * 6, flags); 302 1.1 christos expect_ptr_not_null(p, "regrow failed"); 303 1.1 christos expect_ptr_eq(p, original, "regrow should preserve address"); 304 1.1 christos 305 1.1 christos dallocx(p, MALLOCX_TCACHE_NONE); 306 1.1 christos 307 1.1 christos char buf[64]; 308 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 309 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 310 1.1 christos "arena destroy failed"); 311 1.1 christos } 312 1.1 christos TEST_END 313 1.1 christos 314 1.1 christos TEST_BEGIN(test_pinned_reset) { 315 1.1 christos pinned_hooks_reset(); 316 1.1 christos unsigned arena_ind; 317 1.1 christos size_t sz = sizeof(arena_ind); 318 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 319 1.1 christos 320 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 321 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 322 1.1 christos "arena creation failed"); 323 1.1 christos 324 1.1 christos int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; 325 1.1 christos 326 1.1 christos /* Allocate several pinned extents, leave them live. */ 327 1.1 christos void *ptrs[4]; 328 1.1 christos for (int i = 0; i < 4; i++) { 329 1.1 christos ptrs[i] = mallocx(SC_LARGE_MINCLASS * (i + 1), flags); 330 1.1 christos expect_ptr_not_null(ptrs[i], "alloc %d failed", i); 331 1.1 christos } 332 1.1 christos 333 1.1 christos /* 334 1.1 christos * arena.<i>.reset returns live allocations to the caches without 335 1.1 christos * destroying the arena. No destroy hook calls should fire (pinned 336 1.1 christos * extents stay in ecache_pinned), and the arena should still be 337 1.1 christos * usable for further allocations afterward. 338 1.1 christos */ 339 1.1 christos unsigned destroy_calls_before = pinned_destroy_calls; 340 1.1 christos char buf[64]; 341 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.reset", arena_ind); 342 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 343 1.1 christos "arena reset failed"); 344 1.1 christos expect_u_eq(pinned_destroy_calls, destroy_calls_before, 345 1.1 christos "reset should not invoke the destroy hook"); 346 1.1 christos 347 1.1 christos tsd_t *tsd = tsd_fetch(); 348 1.1 christos arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false); 349 1.1 christos expect_ptr_not_null(arena, "arena_get failed"); 350 1.1 christos expect_zu_gt(ecache_npages_get(&arena->pa_shard.pac.ecache_pinned), 0, 351 1.1 christos "pinned ecache should hold the reset extents"); 352 1.1 christos 353 1.1 christos /* Arena is still usable: alloc and free again. */ 354 1.1 christos void *p = mallocx(SC_LARGE_MINCLASS, flags); 355 1.1 christos expect_ptr_not_null(p, "post-reset alloc failed"); 356 1.1 christos dallocx(p, MALLOCX_TCACHE_NONE); 357 1.1 christos 358 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 359 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 360 1.1 christos "arena destroy failed"); 361 1.1 christos } 362 1.1 christos TEST_END 363 1.1 christos 364 1.1 christos TEST_BEGIN(test_pinned_destroy) { 365 1.1 christos pinned_hooks_reset(); 366 1.1 christos 367 1.1 christos unsigned arena_ind; 368 1.1 christos size_t sz = sizeof(arena_ind); 369 1.1 christos extent_hooks_t *hooks_ptr = &pinned_hooks; 370 1.1 christos expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz, 371 1.1 christos &hooks_ptr, sizeof(hooks_ptr)), 372 1.1 christos "arena creation failed"); 373 1.1 christos 374 1.1 christos int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE; 375 1.1 christos 376 1.1 christos size_t mapped_initial = config_stats ? get_arena_mapped(arena_ind) : 0; 377 1.1 christos 378 1.1 christos void *ptrs[4]; 379 1.1 christos size_t sizes[4] = {PAGE * 4, SC_LARGE_MINCLASS, 380 1.1 christos SC_LARGE_MINCLASS + PAGE, SC_LARGE_MINCLASS * 2}; 381 1.1 christos size_t total = 0; 382 1.1 christos for (int i = 0; i < 4; i++) { 383 1.1 christos ptrs[i] = mallocx(sizes[i], flags); 384 1.1 christos expect_ptr_not_null(ptrs[i], "alloc %d failed", i); 385 1.1 christos total += sizes[i]; 386 1.1 christos } 387 1.1 christos 388 1.1 christos if (config_stats) { 389 1.1 christos expect_zu_ge(get_arena_mapped(arena_ind) - mapped_initial, 390 1.1 christos total, 391 1.1 christos "mapped should grow by at least total after pinned allocs"); 392 1.1 christos } 393 1.1 christos 394 1.1 christos /* 395 1.1 christos * Stress alloc/dalloc/realloc churn before the final teardown. Mix 396 1.1 christos * sizes (some at the slab boundary, some multi-large-class), and on 397 1.1 christos * platforms that support splitting, alternate shrink and regrow on 398 1.1 christos * the same extent to stress the in-place realloc accounting paths. 399 1.1 christos */ 400 1.1 christos bool can_split = !ehooks_default_split_impl(); 401 1.1 christos for (int round = 0; round < 64; round++) { 402 1.1 christos size_t s = SC_LARGE_MINCLASS + PAGE * (round % 4); 403 1.1 christos void *p = mallocx(s, flags); 404 1.1 christos expect_ptr_not_null(p, "churn alloc %d failed", round); 405 1.1 christos if (can_split && (round & 1)) { 406 1.1 christos void *shrunk = rallocx(p, SC_LARGE_MINCLASS, flags); 407 1.1 christos expect_ptr_not_null(shrunk, "churn shrink %d failed", 408 1.1 christos round); 409 1.1 christos void *regrown = rallocx(shrunk, SC_LARGE_MINCLASS * 3, 410 1.1 christos flags); 411 1.1 christos expect_ptr_not_null(regrown, "churn regrow %d failed", 412 1.1 christos round); 413 1.1 christos dallocx(regrown, MALLOCX_TCACHE_NONE); 414 1.1 christos } else { 415 1.1 christos void *p2 = rallocx(p, SC_LARGE_MINCLASS * 2, flags); 416 1.1 christos expect_ptr_not_null(p2, "churn realloc %d failed", 417 1.1 christos round); 418 1.1 christos dallocx(p2, MALLOCX_TCACHE_NONE); 419 1.1 christos } 420 1.1 christos } 421 1.1 christos /* Free and re-alloc one of the original pointers to exercise reuse. */ 422 1.1 christos dallocx(ptrs[1], MALLOCX_TCACHE_NONE); 423 1.1 christos ptrs[1] = mallocx(sizes[1], flags); 424 1.1 christos expect_ptr_not_null(ptrs[1], "reuse alloc failed"); 425 1.1 christos 426 1.1 christos for (int i = 0; i < 4; i++) { 427 1.1 christos dallocx(ptrs[i], MALLOCX_TCACHE_NONE); 428 1.1 christos } 429 1.1 christos 430 1.1 christos if (config_stats) { 431 1.1 christos expect_zu_ge(get_arena_mapped(arena_ind) - mapped_initial, 432 1.1 christos total, 433 1.1 christos "pinned bytes should remain in mapped after dalloc " 434 1.1 christos "(no decay for pinned)"); 435 1.1 christos } 436 1.1 christos 437 1.1 christos tsd_t *tsd = tsd_fetch(); 438 1.1 christos tsdn_t *tsdn = tsd_tsdn(tsd); 439 1.1 christos arena_t *arena = arena_get(tsdn, arena_ind, false); 440 1.1 christos expect_ptr_not_null(arena, "arena_get failed"); 441 1.1 christos size_t pinned_bytes = 442 1.1 christos ecache_npages_get(&arena->pa_shard.pac.ecache_pinned) << LG_PAGE; 443 1.1 christos expect_zu_gt(pinned_bytes, 0, 444 1.1 christos "pinned ecache should contain the freed extents"); 445 1.1 christos 446 1.1 christos /* 447 1.1 christos * Pinned extents stay registered in the emap after dalloc. Coalescing 448 1.1 christos * may have made any individual ptrs[i] interior to a larger merged 449 1.1 christos * extent, so use find_covering_edata to walk to the merged extent's 450 1.1 christos * base/last-page; each must resolve to a pinned-state edata. The 451 1.1 christos * search bound is the total bytes currently in ecache_pinned, which 452 1.1 christos * upper-bounds the size of any covering merged extent. 453 1.1 christos */ 454 1.1 christos for (int i = 0; i < 4; i++) { 455 1.1 christos edata_t *covering = find_covering_edata(ptrs[i], 456 1.1 christos pinned_bytes); 457 1.1 christos expect_ptr_not_null(covering, 458 1.1 christos "freed pinned extent ptrs[%d] should still be reachable " 459 1.1 christos "in the emap", i); 460 1.1 christos expect_d_eq(edata_state_get(covering), extent_state_pinned, 461 1.1 christos "covering extent for ptrs[%d] should be in pinned state", 462 1.1 christos i); 463 1.1 christos } 464 1.1 christos 465 1.1 christos char buf[64]; 466 1.1 christos snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind); 467 1.1 christos unsigned destroy_calls_before = pinned_destroy_calls; 468 1.1 christos size_t destroy_bytes_before = pinned_destroy_bytes; 469 1.1 christos expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0), 470 1.1 christos "arena destroy failed"); 471 1.1 christos expect_u_gt(pinned_destroy_calls, destroy_calls_before, 472 1.1 christos "arena destroy should invoke the destroy hook"); 473 1.1 christos expect_zu_ge(pinned_destroy_bytes - destroy_bytes_before, total, 474 1.1 christos "destroy hook should be called with at least the allocated total " 475 1.1 christos "(coalesced fragments returned to OS)"); 476 1.1 christos if (maps_coalesce) { 477 1.1 christos expect_u_lt(pinned_destroy_calls - destroy_calls_before, 4, 478 1.1 christos "destroy calls should be < #allocs after coalesce"); 479 1.1 christos } 480 1.1 christos for (int i = 0; i < 4; i++) { 481 1.1 christos expect_ptr_null(find_covering_edata(ptrs[i], pinned_bytes), 482 1.1 christos "arena destroy should clear the emap entry for ptrs[%d]", 483 1.1 christos i); 484 1.1 christos } 485 1.1 christos } 486 1.1 christos TEST_END 487 1.1 christos 488 1.1 christos int 489 1.1 christos main(void) { 490 1.1 christos return test_no_reentrancy( 491 1.1 christos test_pinned_remnant_lock, 492 1.1 christos test_pinned_reuse, 493 1.1 christos test_pinned_realloc, 494 1.1 christos test_pinned_stats, 495 1.1 christos test_pinned_shrink, 496 1.1 christos test_pinned_reset, 497 1.1 christos test_pinned_destroy); 498 1.1 christos } 499