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