From ebdd539dc22f15de412ac91bae8c0861035df671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Mar=C3=ADn?= Date: Thu, 16 Jul 2026 17:56:40 +0200 Subject: [PATCH] Address review: exercise arena-level alloc/free in the percpu resume test Allocate and free through each arena binding while bypassing the tcache (MALLOCX_TCACHE_NONE), so the allocations and deallocations reach the arena rather than the thread cache. Assert via arenas.lookup that a region allocated while bound to the manual arena comes from it, and that after resuming per-CPU selection allocations come from a per-CPU arena. Finally free the manual-arena region while the thread is bound to a per-CPU arena, covering deallocation routing across the rebinding. Co-Authored-By: Claude Opus 4.8 (1M context) --- test/unit/percpu_arena_resume.c | 69 ++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/test/unit/percpu_arena_resume.c b/test/unit/percpu_arena_resume.c index 117cd9bf..6023d3fa 100644 --- a/test/unit/percpu_arena_resume.c +++ b/test/unit/percpu_arena_resume.c @@ -1,69 +1,76 @@ #include "test/jemalloc_test.h" /* - * When percpu_arena is enabled a thread is bound to a manually created arena - * (an index at or above the per-CPU auto range) to route a bounded region of - * work to a dedicated arena, then handed back to automatic per-CPU selection. - * - * Setting thread.arena to an index within the per-CPU auto range is the signal - * to resume per-CPU management. Without it a thread bound to a manual arena is - * never reclaimed by percpu (see arena_choose_impl), so it would stay pinned to - * the manual arena forever and its later allocations would land there instead - * of following the CPU. + * Under percpu_arena, binding a thread to a manual arena (an index at or above + * the per-CPU auto range) is one-way: percpu never reclaims it (see + * arena_choose_impl). Setting thread.arena back to an index in the auto range + * resumes per-CPU selection instead of failing with EPERM. */ TEST_BEGIN(test_thread_arena_resume_percpu) { test_skip_if(!have_percpu_arena || !PERCPU_ARENA_ENABLED(opt_percpu_arena)); unsigned limit = percpu_arena_ind_limit(opt_percpu_arena); + /* Bypass the tcache so every allocation and free hits the arena. */ + const int flags = MALLOCX_TCACHE_NONE; - /* Force the thread to be bound to its current CPU's arena. */ - void *p = mallocx(1, 0); - expect_ptr_not_null(p, "Unexpected mallocx() failure"); + void *warm = mallocx(1, 0); + expect_ptr_not_null(warm, "Unexpected mallocx() failure"); + dallocx(warm, 0); unsigned cur; size_t sz = sizeof(cur); expect_d_eq(mallctl("thread.arena", (void *)&cur, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); - expect_u_lt(cur, limit, - "Thread should start bound to a per-CPU (auto) arena"); + expect_u_lt(cur, limit, "Thread should start on a per-CPU arena"); - /* Create a dedicated arena, which is always outside the auto range. */ unsigned manual; sz = sizeof(manual); expect_d_eq(mallctl("arenas.create", (void *)&manual, &sz, NULL, 0), 0, "Unexpected arenas.create() failure"); - expect_u_ge(manual, limit, - "A manually created arena must be outside the per-CPU range"); + expect_u_ge(manual, limit, "A manual arena is outside the per-CPU range"); - /* Binding to a manual arena is allowed and takes effect. */ unsigned old; sz = sizeof(old); expect_d_eq(mallctl("thread.arena", (void *)&old, &sz, (void *)&manual, - sizeof(manual)), 0, - "Binding to a manual arena should be allowed under percpu"); + sizeof(manual)), 0, "Binding to a manual arena should be allowed"); sz = sizeof(cur); expect_d_eq(mallctl("thread.arena", (void *)&cur, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); expect_u_eq(cur, manual, "Thread should be bound to the manual arena"); - /* - * Setting thread.arena to an index in the per-CPU range resumes - * automatic per-CPU selection instead of returning EPERM. - */ + void *p_manual = mallocx(1024, flags); + expect_ptr_not_null(p_manual, "Unexpected mallocx() failure"); + unsigned found; + sz = sizeof(found); + expect_d_eq(mallctl("arenas.lookup", (void *)&found, &sz, + (void *)&p_manual, sizeof(p_manual)), 0, + "Unexpected arenas.lookup() failure"); + expect_u_eq(found, manual, "Allocation should come from the manual arena"); + + void *scratch = mallocx(1024, flags); + expect_ptr_not_null(scratch, "Unexpected mallocx() failure"); + dallocx(scratch, flags); + unsigned resume = 0; expect_d_eq(mallctl("thread.arena", NULL, NULL, (void *)&resume, - sizeof(resume)), 0, - "Setting thread.arena within the per-CPU range should resume " - "per-CPU selection, not fail"); - + sizeof(resume)), 0, "Should resume per-CPU selection, not fail"); sz = sizeof(cur); expect_d_eq(mallctl("thread.arena", (void *)&cur, &sz, NULL, 0), 0, "Unexpected mallctl() failure"); - expect_u_lt(cur, limit, - "Thread should be back under per-CPU management after resuming"); + expect_u_lt(cur, limit, "Thread should be back on a per-CPU arena"); - dallocx(p, 0); + void *p_percpu = mallocx(1024, flags); + expect_ptr_not_null(p_percpu, "Unexpected mallocx() failure"); + sz = sizeof(found); + expect_d_eq(mallctl("arenas.lookup", (void *)&found, &sz, + (void *)&p_percpu, sizeof(p_percpu)), 0, + "Unexpected arenas.lookup() failure"); + expect_u_lt(found, limit, "Allocation should come from a per-CPU arena"); + dallocx(p_percpu, flags); + + /* Free the manual-arena region while bound to a different arena. */ + dallocx(p_manual, flags); } TEST_END