mirror of
https://github.com/jemalloc/jemalloc.git
synced 2026-07-18 14:47:18 +03:00
Add PAC decay/deferral unit test coverage.
Add unit tests to cover public interface of pac decay & deferral relevant functions. To align with the decopuling between arena and pac, rename arena_decay to pac_decay here.
This commit is contained in:
parent
d410f43e1f
commit
e931730f51
5 changed files with 144 additions and 8 deletions
|
|
@ -204,7 +204,6 @@ C_UTIL_CPP_SRCS := $(srcroot)src/nstime.c $(srcroot)src/malloc_io.c
|
|||
endif
|
||||
TESTS_UNIT := \
|
||||
$(srcroot)test/unit/a0.c \
|
||||
$(srcroot)test/unit/arena_decay.c \
|
||||
$(srcroot)test/unit/arena_reset.c \
|
||||
$(srcroot)test/unit/arenas_management.c \
|
||||
$(srcroot)test/unit/atomic.c \
|
||||
|
|
@ -272,6 +271,7 @@ TESTS_UNIT := \
|
|||
$(srcroot)test/unit/oversize_threshold.c \
|
||||
$(srcroot)test/unit/pa.c \
|
||||
$(srcroot)test/unit/pac.c \
|
||||
$(srcroot)test/unit/pac_decay.c \
|
||||
$(srcroot)test/unit/pac_sec_integration.c \
|
||||
$(srcroot)test/unit/pack.c \
|
||||
$(srcroot)test/unit/pages.c \
|
||||
|
|
|
|||
|
|
@ -273,9 +273,67 @@ TEST_BEGIN(test_decay_ns_until_purge) {
|
|||
}
|
||||
TEST_END
|
||||
|
||||
/*
|
||||
* Branch coverage for the decay-level math the early-wake path
|
||||
* (pac_decay_should_wake_early) relies on, exercised here at the public decay
|
||||
* seam with explicit nstime rather than via real-thread timing.
|
||||
*/
|
||||
TEST_BEGIN(test_decay_early_wake_branches) {
|
||||
decay_t decay;
|
||||
memset(&decay, 0, sizeof(decay));
|
||||
|
||||
nstime_t curtime;
|
||||
nstime_init(&curtime, 0);
|
||||
|
||||
uint64_t decay_ms = 1000;
|
||||
nstime_t decay_nstime;
|
||||
nstime_init(&decay_nstime, decay_ms * 1000 * 1000);
|
||||
expect_false(decay_init(&decay, &curtime, (ssize_t)decay_ms),
|
||||
"Failed to initialize decay");
|
||||
|
||||
size_t new_pages = 100;
|
||||
|
||||
/*
|
||||
* Parity trap: with zero new pages the accumulation contributes
|
||||
* nothing, so decay_npages_purge_in must return 0 regardless of the
|
||||
* remaining-sleep window. (The early-wake THRESHOLD compare on the
|
||||
* EXISTING backlog still runs in pac_decay_should_wake_early; that is
|
||||
* deliberately kept and documented in src/pac.c.)
|
||||
*/
|
||||
nstime_t window;
|
||||
nstime_copy(&window, &decay_nstime);
|
||||
expect_u64_eq(decay_npages_purge_in(&decay, &window, 0), 0,
|
||||
"Zero new pages must contribute zero to the purge estimate");
|
||||
|
||||
/*
|
||||
* A remaining-sleep window at/after the full decay period caps the
|
||||
* estimate at the new-page count (all of them will have decayed).
|
||||
*/
|
||||
expect_u64_eq(decay_npages_purge_in(&decay, &window, new_pages),
|
||||
new_pages, "All new pages should be due within the decay window");
|
||||
|
||||
nstime_t beyond;
|
||||
nstime_copy(&beyond, &decay_nstime);
|
||||
nstime_add(&beyond, &decay_nstime);
|
||||
expect_u64_eq(decay_npages_purge_in(&decay, &beyond, new_pages),
|
||||
new_pages,
|
||||
"Estimate must cap at the new-page count beyond the decay window");
|
||||
|
||||
/*
|
||||
* No-backlog branch: with a zero threshold, decay_ns_until_purge is
|
||||
* unbounded (nothing schedulable). This mirrors the indefinite-sleep
|
||||
* case.
|
||||
*/
|
||||
expect_u64_eq(decay_ns_until_purge(&decay, 0, 0),
|
||||
DECAY_UNBOUNDED_TIME_TO_PURGE,
|
||||
"Zero threshold must yield an unbounded time-to-purge");
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int
|
||||
main(void) {
|
||||
return test(test_decay_init, test_decay_ms_valid,
|
||||
test_decay_npages_purge_in, test_decay_maybe_advance_epoch,
|
||||
test_decay_empty, test_decay, test_decay_ns_until_purge);
|
||||
test_decay_empty, test_decay, test_decay_ns_until_purge,
|
||||
test_decay_early_wake_branches);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
#include "jemalloc/internal/pa.h"
|
||||
|
||||
/*
|
||||
* pac_decay_deferred is JET_EXTERN (test-only; see src/pac.c) and has no public
|
||||
* header declaration, so declare it locally for the deterministic test below.
|
||||
*/
|
||||
extern void pac_decay_deferred(tsdn_t *tsdn, pac_t *pac,
|
||||
pac_purge_eagerness_t eagerness, pac_deferred_work_result_t *result);
|
||||
|
||||
#define PAC_TEST_ARENA_IND 124
|
||||
|
||||
static bool pac_test_dalloc_fail;
|
||||
|
|
@ -114,12 +121,14 @@ pac_test_data_init(bool custom_hooks, ssize_t dirty_decay_ms,
|
|||
|
||||
nstime_t time;
|
||||
nstime_init(&time, 0);
|
||||
assert_false(pa_central_init(&data->central, data->base, /* hpa */ false,
|
||||
&hpa_hooks_default), "Unexpected pa_central_init failure");
|
||||
assert_false(pa_central_init(&data->central, data->base,
|
||||
/* hpa */ false, &hpa_hooks_default),
|
||||
"Unexpected pa_central_init failure");
|
||||
assert_false(pa_shard_init(tsdn, &data->shard, &data->central,
|
||||
&data->emap, data->base, PAC_TEST_ARENA_IND, &data->stats,
|
||||
&data->stats_mtx, &time, SC_LARGE_MAXCLASS + PAGE,
|
||||
dirty_decay_ms, muzzy_decay_ms), "Unexpected pa_shard_init failure");
|
||||
dirty_decay_ms, muzzy_decay_ms),
|
||||
"Unexpected pa_shard_init failure");
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
@ -382,6 +391,45 @@ TEST_BEGIN(test_pac_large_guarded_dalloc_unguards_before_caching) {
|
|||
}
|
||||
TEST_END
|
||||
|
||||
TEST_BEGIN(test_pac_deferred_never_does_not_purge) {
|
||||
/*
|
||||
* PAC_PURGE_NEVER is the eagerness the application free path selects
|
||||
* when a background thread is enabled, meaning current thread does not
|
||||
* purge. It is only reachable through the exposed pac_decay_deferred
|
||||
* primitive (the production entry pac_do_deferred_work picks eagerness
|
||||
* itself), so this is a deliberate internal test of that critical,
|
||||
* stable edge.
|
||||
*/
|
||||
pac_test_hooks_reset();
|
||||
/*
|
||||
* Finite dirty decay so a purge is gated by eagerness, not decay_ms<=0.
|
||||
*/
|
||||
pac_test_data_t *data = pac_test_data_init(
|
||||
/* custom_hooks */ true, /* dirty_decay_ms */ 100 * 1000, -1);
|
||||
pac_t *pac = &data->shard.pac;
|
||||
|
||||
edata_t *edata = pac_alloc_expect(data, PAGE, /* guarded */ false);
|
||||
size_t ndirty_before_dalloc = ecache_npages_get(&pac->ecache_dirty);
|
||||
|
||||
/* Dalloc caches the extent as dirty and requests deferred work. */
|
||||
bool deferred_work_generated = false;
|
||||
pac_dalloc(tsdn_fetch(), pac, edata, &deferred_work_generated);
|
||||
expect_true(deferred_work_generated,
|
||||
"Non-pinned dalloc should request deferred work");
|
||||
size_t ndirty_after_dalloc = ecache_npages_get(&pac->ecache_dirty);
|
||||
expect_zu_gt(ndirty_after_dalloc, ndirty_before_dalloc,
|
||||
"Dalloc should add dirty pages");
|
||||
|
||||
/* PAC_PURGE_NEVER must leave the dirty pages untouched. */
|
||||
pac_deferred_work_result_t result;
|
||||
pac_decay_deferred(tsdn_fetch(), pac, PAC_PURGE_NEVER, &result);
|
||||
expect_zu_eq(ecache_npages_get(&pac->ecache_dirty), ndirty_after_dalloc,
|
||||
"PAC_PURGE_NEVER must not purge any dirty pages");
|
||||
|
||||
pac_test_data_destroy(data);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int
|
||||
main(void) {
|
||||
return test_no_reentrancy(test_pac_dirty_muzzy_alloc_priority,
|
||||
|
|
@ -391,5 +439,6 @@ main(void) {
|
|||
test_pac_decay_retains_when_dalloc_fails,
|
||||
test_pac_non_pinned_dalloc_signals_deferred_work,
|
||||
test_pac_non_pinned_shrink_signals_deferred_work,
|
||||
test_pac_large_guarded_dalloc_unguards_before_caching);
|
||||
test_pac_large_guarded_dalloc_unguards_before_caching,
|
||||
test_pac_deferred_never_does_not_purge);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include "test/jemalloc_test.h"
|
||||
#include "test/arena_util.h"
|
||||
|
||||
#include "jemalloc/internal/deferral.h"
|
||||
#include "jemalloc/internal/pac.h"
|
||||
#include "jemalloc/internal/ticker.h"
|
||||
|
||||
static nstime_monotonic_t *nstime_monotonic_orig;
|
||||
|
|
@ -439,8 +441,35 @@ TEST_BEGIN(test_decay_never) {
|
|||
}
|
||||
TEST_END
|
||||
|
||||
/*
|
||||
* pac_time_until_deferred_work is the public PAC scheduling entry the
|
||||
* background thread uses to decide its next wakeup. Exercised here on a real
|
||||
* arena through the public interface (no direct decay-state manipulation).
|
||||
*/
|
||||
TEST_BEGIN(test_pac_time_until_deferred_work) {
|
||||
test_skip_if(is_background_thread_enabled());
|
||||
test_skip_if(opt_hpa);
|
||||
|
||||
unsigned arena_ind = do_arena_create(1000, -1);
|
||||
tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
|
||||
pac_t *pac = &arena_get(tsdn, arena_ind, false)->pa_shard.pac;
|
||||
|
||||
/* Idle: nothing pending -> the scheduler reports the maximum wait. */
|
||||
expect_u64_eq(pac_time_until_deferred_work(tsdn, pac), DEFERRED_WORK_MAX,
|
||||
"Idle PAC should defer for the maximum interval");
|
||||
|
||||
/* Dirty pages under a finite decay_ms -> a finite wait is scheduled. */
|
||||
generate_dirty(arena_ind, PAGE);
|
||||
expect_u64_lt(pac_time_until_deferred_work(tsdn, pac), DEFERRED_WORK_MAX,
|
||||
"Pending dirty decay should schedule a finite deferred-work time");
|
||||
|
||||
do_arena_destroy(arena_ind);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int
|
||||
main(void) {
|
||||
return test(test_decay_ticks, test_decay_ticker,
|
||||
test_decay_nonmonotonic, test_decay_now, test_decay_never);
|
||||
test_decay_nonmonotonic, test_decay_now, test_decay_never,
|
||||
test_pac_time_until_deferred_work);
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
export MALLOC_CONF="dirty_decay_ms:1000,muzzy_decay_ms:1000,tcache_max:1024"
|
||||
export MALLOC_CONF="dirty_decay_ms:1000,muzzy_decay_ms:1000,tcache_max:1024,background_thread:false"
|
||||
Loading…
Add table
Add a link
Reference in a new issue