mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-07-18 14:47:18 +03:00
Allow resuming per-CPU arena selection via thread.arena
With percpu_arena enabled, thread.arena control is one-directional. A thread can be 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, long-lived arena, but there is no way back: thread_arena_ctl returns EPERM for any index within the auto range, and arena_choose_impl only re-selects a per-CPU arena for threads whose current arena is already in that range. So once a thread is bound to a manual arena it stays pinned there forever, and its later allocations land there instead of following the CPU. Treat setting thread.arena to an index within the per-CPU range as a request to resume automatic per-CPU selection: hand the thread back to percpu management (rebinding it to the current CPU's arena via percpu_arena_update) and return 0 instead of EPERM. Binding to a manual arena is unchanged. The requested index is advisory; under percpu the thread is governed by its current CPU, so it resumes on the current CPU's arena regardless of the value passed. Add test/unit/percpu_arena_resume covering the manual-arena to resume round trip, and update test_thread_arena, which asserted the old EPERM. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8361239b03
commit
033c108750
5 changed files with 97 additions and 6 deletions
|
|
@ -276,6 +276,7 @@ TESTS_UNIT := \
|
|||
$(srcroot)test/unit/pack.c \
|
||||
$(srcroot)test/unit/pages.c \
|
||||
$(srcroot)test/unit/peak.c \
|
||||
$(srcroot)test/unit/percpu_arena_resume.c \
|
||||
$(srcroot)test/unit/ph.c \
|
||||
$(srcroot)test/unit/prng.c \
|
||||
$(srcroot)test/unit/prof_accum.c \
|
||||
|
|
|
|||
14
src/ctl.c
14
src/ctl.c
|
|
@ -2374,10 +2374,18 @@ thread_arena_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
|
|||
if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)) {
|
||||
if (newind < percpu_arena_ind_limit(opt_percpu_arena)) {
|
||||
/*
|
||||
* If perCPU arena is enabled, thread_arena control is
|
||||
* not allowed for the auto arena range.
|
||||
* Setting thread.arena to an arena in the auto range
|
||||
* means "resume automatic per-CPU selection" rather than
|
||||
* pinning to a specific per-CPU arena. This lets a caller
|
||||
* that temporarily switched to a manually managed arena
|
||||
* (e.g. a scoped guard) hand the thread back to per-CPU
|
||||
* management. It is otherwise impossible: a thread bound
|
||||
* to a manual arena is never reclaimed by percpu (see
|
||||
* arena_choose_impl), so without this it would stay
|
||||
* pinned forever.
|
||||
*/
|
||||
return EPERM;
|
||||
percpu_arena_update(tsd, percpu_arena_choose());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -764,12 +764,17 @@ TEST_BEGIN(test_thread_arena) {
|
|||
0, "Unexpected mallctl() failure");
|
||||
new_arena_ind = percpu_arena_ind_limit(opt_percpu_arena) - 1;
|
||||
if (old_arena_ind != new_arena_ind) {
|
||||
/*
|
||||
* Setting thread.arena to an index within the per-CPU
|
||||
* range resumes automatic per-CPU selection rather than
|
||||
* failing (see test/unit/percpu_arena_resume.c).
|
||||
*/
|
||||
expect_d_eq(
|
||||
mallctl("thread.arena", (void *)&old_arena_ind, &sz,
|
||||
(void *)&new_arena_ind, sizeof(unsigned)),
|
||||
EPERM,
|
||||
"thread.arena ctl "
|
||||
"should not be allowed with percpu arena");
|
||||
0,
|
||||
"thread.arena within the per-CPU range should "
|
||||
"resume per-CPU selection");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
74
test/unit/percpu_arena_resume.c
Normal file
74
test/unit/percpu_arena_resume.c
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#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.
|
||||
*/
|
||||
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);
|
||||
|
||||
/* 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");
|
||||
|
||||
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");
|
||||
|
||||
/* 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");
|
||||
|
||||
/* 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");
|
||||
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.
|
||||
*/
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
dallocx(p, 0);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int
|
||||
main(void) {
|
||||
return test(
|
||||
test_thread_arena_resume_percpu);
|
||||
}
|
||||
3
test/unit/percpu_arena_resume.sh
Normal file
3
test/unit/percpu_arena_resume.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
export MALLOC_CONF="percpu_arena:percpu"
|
||||
Loading…
Add table
Add a link
Reference in a new issue