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) <noreply@anthropic.com>
This commit is contained in:
Raúl Marín 2026-07-16 17:56:40 +02:00
parent 033c108750
commit ebdd539dc2

View file

@ -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