Revert PR #2608: Manually revert commits 70c94d..f9c0b5

This commit is contained in:
Shirui Cheng 2025-07-15 15:44:14 -07:00 committed by Guangli Dai
parent 9186700eb3
commit e2da7477f8
30 changed files with 124 additions and 1364 deletions

View file

@ -34,8 +34,6 @@ main(void) {
P(arena_t);
P(arena_stats_t);
P(base_t);
P(bin_t);
P(bin_with_batch_t);
P(decay_t);
P(edata_t);
P(ecache_t);

View file

@ -1,34 +0,0 @@
#ifndef JEMALLOC_TEST_FORK_H
#define JEMALLOC_TEST_FORK_H
#ifndef _WIN32
# include <sys/wait.h>
static inline void
fork_wait_for_child_exit(int pid) {
int status;
while (true) {
if (waitpid(pid, &status, 0) == -1) {
test_fail("Unexpected waitpid() failure.");
}
if (WIFSIGNALED(status)) {
test_fail(
"Unexpected child termination due to "
"signal %d",
WTERMSIG(status));
break;
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
test_fail("Unexpected child exit value %d",
WEXITSTATUS(status));
}
break;
}
}
}
#endif
#endif /* JEMALLOC_TEST_FORK_H */

View file

@ -1,243 +0,0 @@
#include "test/jemalloc_test.h"
#include "jemalloc/internal/batcher.h"
TEST_BEGIN(test_simple) {
enum { NELEMS_MAX = 10, DATA_BASE_VAL = 100, NRUNS = 5 };
batcher_t batcher;
size_t data[NELEMS_MAX];
for (size_t nelems = 0; nelems < NELEMS_MAX; nelems++) {
batcher_init(&batcher, nelems);
for (int run = 0; run < NRUNS; run++) {
for (int i = 0; i < NELEMS_MAX; i++) {
data[i] = (size_t)-1;
}
for (size_t i = 0; i < nelems; i++) {
size_t idx = batcher_push_begin(
TSDN_NULL, &batcher, 1);
assert_zu_eq(i, idx, "Wrong index");
assert_zu_eq((size_t)-1, data[idx],
"Expected uninitialized slot");
data[idx] = DATA_BASE_VAL + i;
batcher_push_end(TSDN_NULL, &batcher);
}
if (nelems > 0) {
size_t idx = batcher_push_begin(
TSDN_NULL, &batcher, 1);
assert_zu_eq(BATCHER_NO_IDX, idx,
"Shouldn't be able to push into a full "
"batcher");
}
size_t npop = batcher_pop_begin(TSDN_NULL, &batcher);
if (nelems == 0) {
assert_zu_eq(npop, BATCHER_NO_IDX,
"Shouldn't get any items out of an empty "
"batcher");
} else {
assert_zu_eq(npop, nelems,
"Wrong number of elements popped");
}
for (size_t i = 0; i < nelems; i++) {
assert_zu_eq(data[i], DATA_BASE_VAL + i,
"Item popped out of order!");
}
if (nelems != 0) {
batcher_pop_end(TSDN_NULL, &batcher);
}
}
}
}
TEST_END
TEST_BEGIN(test_multi_push) {
size_t idx, nelems;
batcher_t batcher;
batcher_init(&batcher, 11);
/* Push two at a time, 5 times, for 10 total. */
for (int i = 0; i < 5; i++) {
idx = batcher_push_begin(TSDN_NULL, &batcher, 2);
assert_zu_eq(2 * i, idx, "Should push in order");
batcher_push_end(TSDN_NULL, &batcher);
}
/* Pushing two more should fail -- would put us at 12 elems. */
idx = batcher_push_begin(TSDN_NULL, &batcher, 2);
assert_zu_eq(BATCHER_NO_IDX, idx, "Should be out of space");
/* But one more should work */
idx = batcher_push_begin(TSDN_NULL, &batcher, 1);
assert_zu_eq(10, idx, "Should be out of space");
batcher_push_end(TSDN_NULL, &batcher);
nelems = batcher_pop_begin(TSDN_NULL, &batcher);
batcher_pop_end(TSDN_NULL, &batcher);
assert_zu_eq(11, nelems, "Should have popped everything");
}
TEST_END
enum {
STRESS_TEST_ELEMS = 10,
STRESS_TEST_THREADS = 4,
STRESS_TEST_OPS = 1000 * 1000,
STRESS_TEST_PUSH_TO_POP_RATIO = 5,
};
typedef struct stress_test_data_s stress_test_data_t;
struct stress_test_data_s {
batcher_t batcher;
mtx_t pop_mtx;
atomic_u32_t thread_id;
uint32_t elems_data[STRESS_TEST_ELEMS];
size_t push_count[STRESS_TEST_ELEMS];
size_t pop_count[STRESS_TEST_ELEMS];
atomic_zu_t atomic_push_count[STRESS_TEST_ELEMS];
atomic_zu_t atomic_pop_count[STRESS_TEST_ELEMS];
};
/*
* Note: 0-indexed. If one element is set and you want to find it, you call
* get_nth_set(elems, 0).
*/
static size_t
get_nth_set(bool elems_owned[STRESS_TEST_ELEMS], size_t n) {
size_t ntrue = 0;
for (size_t i = 0; i < STRESS_TEST_ELEMS; i++) {
if (elems_owned[i]) {
ntrue++;
}
if (ntrue > n) {
return i;
}
}
assert_not_reached(
"Asked for the %zu'th set element when < %zu are "
"set",
n, n);
/* Just to silence a compiler warning. */
return 0;
}
static void *
stress_test_thd(void *arg) {
stress_test_data_t *data = arg;
size_t prng = atomic_fetch_add_u32(&data->thread_id, 1, ATOMIC_RELAXED);
size_t nelems_owned = 0;
bool elems_owned[STRESS_TEST_ELEMS] = {0};
size_t local_push_count[STRESS_TEST_ELEMS] = {0};
size_t local_pop_count[STRESS_TEST_ELEMS] = {0};
for (int i = 0; i < STRESS_TEST_OPS; i++) {
size_t rnd = prng_range_zu(
&prng, STRESS_TEST_PUSH_TO_POP_RATIO);
if (rnd == 0 || nelems_owned == 0) {
size_t nelems = batcher_pop_begin(
TSDN_NULL, &data->batcher);
if (nelems == BATCHER_NO_IDX) {
continue;
}
for (size_t i = 0; i < nelems; i++) {
uint32_t elem = data->elems_data[i];
assert_false(elems_owned[elem],
"Shouldn't already own what we just "
"popped");
elems_owned[elem] = true;
nelems_owned++;
local_pop_count[elem]++;
data->pop_count[elem]++;
}
batcher_pop_end(TSDN_NULL, &data->batcher);
} else {
size_t elem_to_push_idx = prng_range_zu(
&prng, nelems_owned);
size_t elem = get_nth_set(
elems_owned, elem_to_push_idx);
assert_true(elems_owned[elem],
"Should own element we're about to pop");
elems_owned[elem] = false;
local_push_count[elem]++;
data->push_count[elem]++;
nelems_owned--;
size_t idx = batcher_push_begin(
TSDN_NULL, &data->batcher, 1);
assert_zu_ne(idx, BATCHER_NO_IDX,
"Batcher can't be full -- we have one of its "
"elems!");
data->elems_data[idx] = (uint32_t)elem;
batcher_push_end(TSDN_NULL, &data->batcher);
}
}
/* Push all local elems back, flush local counts to the shared ones. */
size_t push_idx = 0;
if (nelems_owned != 0) {
push_idx = batcher_push_begin(
TSDN_NULL, &data->batcher, nelems_owned);
assert_zu_ne(
BATCHER_NO_IDX, push_idx, "Should be space to push");
}
for (size_t i = 0; i < STRESS_TEST_ELEMS; i++) {
if (elems_owned[i]) {
data->elems_data[push_idx] = (uint32_t)i;
push_idx++;
local_push_count[i]++;
data->push_count[i]++;
}
atomic_fetch_add_zu(&data->atomic_push_count[i],
local_push_count[i], ATOMIC_RELAXED);
atomic_fetch_add_zu(&data->atomic_pop_count[i],
local_pop_count[i], ATOMIC_RELAXED);
}
if (nelems_owned != 0) {
batcher_push_end(TSDN_NULL, &data->batcher);
}
return NULL;
}
TEST_BEGIN(test_stress) {
stress_test_data_t data;
batcher_init(&data.batcher, STRESS_TEST_ELEMS);
bool err = mtx_init(&data.pop_mtx);
assert_false(err, "mtx_init failure");
atomic_store_u32(&data.thread_id, 0, ATOMIC_RELAXED);
for (int i = 0; i < STRESS_TEST_ELEMS; i++) {
data.push_count[i] = 0;
data.pop_count[i] = 0;
atomic_store_zu(&data.atomic_push_count[i], 0, ATOMIC_RELAXED);
atomic_store_zu(&data.atomic_pop_count[i], 0, ATOMIC_RELAXED);
size_t idx = batcher_push_begin(TSDN_NULL, &data.batcher, 1);
assert_zu_eq(i, idx, "Should push in order");
data.elems_data[idx] = i;
batcher_push_end(TSDN_NULL, &data.batcher);
}
thd_t threads[STRESS_TEST_THREADS];
for (int i = 0; i < STRESS_TEST_THREADS; i++) {
thd_create(&threads[i], stress_test_thd, &data);
}
for (int i = 0; i < STRESS_TEST_THREADS; i++) {
thd_join(threads[i], NULL);
}
for (int i = 0; i < STRESS_TEST_ELEMS; i++) {
assert_zu_ne(
0, data.push_count[i], "Should have done something!");
assert_zu_eq(data.push_count[i], data.pop_count[i],
"every element should be pushed and popped an equal number "
"of times");
assert_zu_eq(data.push_count[i],
atomic_load_zu(&data.atomic_push_count[i], ATOMIC_RELAXED),
"atomic and non-atomic count should be equal given proper "
"synchronization");
assert_zu_eq(data.pop_count[i],
atomic_load_zu(&data.atomic_pop_count[i], ATOMIC_RELAXED),
"atomic and non-atomic count should be equal given proper "
"synchronization");
}
}
TEST_END
int
main(void) {
return test_no_reentrancy(test_simple, test_multi_push, test_stress);
}

View file

@ -1,270 +0,0 @@
#include "test/jemalloc_test.h"
#include "test/fork.h"
enum {
STRESS_THREADS = 3,
STRESS_OBJECTS_PER_THREAD = 1000,
STRESS_ALLOC_SZ = PAGE / 2,
};
typedef struct stress_thread_data_s stress_thread_data_t;
struct stress_thread_data_s {
unsigned thd_id;
atomic_zu_t *ready_thds;
atomic_zu_t *done_thds;
void **to_dalloc;
};
static atomic_zu_t push_failure_count;
static atomic_zu_t pop_attempt_results[2];
static atomic_zu_t dalloc_zero_slab_count;
static atomic_zu_t dalloc_nonzero_slab_count;
static atomic_zu_t dalloc_nonempty_list_count;
static bool
should_skip() {
return
/*
* We do batching operations on tcache flush pathways; we can't if
* caching is disabled.
*/
!opt_tcache ||
/* We rely on tcache fill/flush operations of the size we use. */
opt_tcache_max < STRESS_ALLOC_SZ
/*
* Some of the races we want to trigger are fiddly enough that they
* only show up under real concurrency. We add 1 to account for the
* main thread, which also does some work.
*/
|| ncpus < STRESS_THREADS + 1;
}
static void
increment_push_failure(size_t push_idx) {
if (push_idx == BATCHER_NO_IDX) {
atomic_fetch_add_zu(&push_failure_count, 1, ATOMIC_RELAXED);
} else {
assert_zu_lt(push_idx, 4, "Only 4 elems");
volatile size_t x = 10000;
while (--x) {
/* Spin for a while, to try to provoke a failure. */
if (x == push_idx) {
#ifdef _WIN32
SwitchToThread();
#else
sched_yield();
#endif
}
}
}
}
static void
increment_pop_attempt(size_t elems_to_pop) {
bool elems = (elems_to_pop != BATCHER_NO_IDX);
atomic_fetch_add_zu(&pop_attempt_results[elems], 1, ATOMIC_RELAXED);
}
static void
increment_slab_dalloc_count(unsigned slab_dalloc_count, bool list_empty) {
if (slab_dalloc_count > 0) {
atomic_fetch_add_zu(
&dalloc_nonzero_slab_count, 1, ATOMIC_RELAXED);
} else {
atomic_fetch_add_zu(&dalloc_zero_slab_count, 1, ATOMIC_RELAXED);
}
if (!list_empty) {
atomic_fetch_add_zu(
&dalloc_nonempty_list_count, 1, ATOMIC_RELAXED);
}
}
static void
flush_tcache() {
assert_d_eq(0, mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
"Unexpected mallctl failure");
}
static void *
stress_thread(void *arg) {
stress_thread_data_t *data = arg;
uint64_t prng_state = data->thd_id;
atomic_fetch_add_zu(data->ready_thds, 1, ATOMIC_RELAXED);
while (atomic_load_zu(data->ready_thds, ATOMIC_RELAXED)
!= STRESS_THREADS) {
/* Spin */
}
for (int i = 0; i < STRESS_OBJECTS_PER_THREAD; i++) {
dallocx(data->to_dalloc[i], 0);
if (prng_range_u64(&prng_state, 3) == 0) {
flush_tcache();
}
}
flush_tcache();
atomic_fetch_add_zu(data->done_thds, 1, ATOMIC_RELAXED);
return NULL;
}
/*
* Run main_thread_fn in conditions that trigger all the various edge cases and
* subtle race conditions.
*/
static void
stress_run(void (*main_thread_fn)(), int nruns) {
bin_batching_test_ndalloc_slabs_max = 1;
bin_batching_test_after_push_hook = &increment_push_failure;
bin_batching_test_mid_pop_hook = &increment_pop_attempt;
bin_batching_test_after_unlock_hook = &increment_slab_dalloc_count;
atomic_store_zu(&push_failure_count, 0, ATOMIC_RELAXED);
atomic_store_zu(&pop_attempt_results[0], 0, ATOMIC_RELAXED);
atomic_store_zu(&pop_attempt_results[1], 0, ATOMIC_RELAXED);
atomic_store_zu(&dalloc_zero_slab_count, 0, ATOMIC_RELAXED);
atomic_store_zu(&dalloc_nonzero_slab_count, 0, ATOMIC_RELAXED);
atomic_store_zu(&dalloc_nonempty_list_count, 0, ATOMIC_RELAXED);
for (int run = 0; run < nruns; run++) {
thd_t thds[STRESS_THREADS];
stress_thread_data_t thd_datas[STRESS_THREADS];
atomic_zu_t ready_thds;
atomic_store_zu(&ready_thds, 0, ATOMIC_RELAXED);
atomic_zu_t done_thds;
atomic_store_zu(&done_thds, 0, ATOMIC_RELAXED);
void *ptrs[STRESS_THREADS][STRESS_OBJECTS_PER_THREAD];
for (int i = 0; i < STRESS_THREADS; i++) {
thd_datas[i].thd_id = i;
thd_datas[i].ready_thds = &ready_thds;
thd_datas[i].done_thds = &done_thds;
thd_datas[i].to_dalloc = ptrs[i];
for (int j = 0; j < STRESS_OBJECTS_PER_THREAD; j++) {
void *ptr = mallocx(STRESS_ALLOC_SZ, 0);
assert_ptr_not_null(ptr, "alloc failure");
ptrs[i][j] = ptr;
}
}
for (int i = 0; i < STRESS_THREADS; i++) {
thd_create(&thds[i], stress_thread, &thd_datas[i]);
}
while (atomic_load_zu(&done_thds, ATOMIC_RELAXED)
!= STRESS_THREADS) {
main_thread_fn();
}
for (int i = 0; i < STRESS_THREADS; i++) {
thd_join(thds[i], NULL);
}
}
bin_batching_test_ndalloc_slabs_max = (unsigned)-1;
bin_batching_test_after_push_hook = NULL;
bin_batching_test_mid_pop_hook = NULL;
bin_batching_test_after_unlock_hook = NULL;
}
static void
do_allocs_frees() {
enum { NALLOCS = 32 };
flush_tcache();
void *ptrs[NALLOCS];
for (int i = 0; i < NALLOCS; i++) {
ptrs[i] = mallocx(STRESS_ALLOC_SZ, 0);
}
for (int i = 0; i < NALLOCS; i++) {
dallocx(ptrs[i], 0);
}
flush_tcache();
}
static void
test_arena_reset_main_fn() {
do_allocs_frees();
}
TEST_BEGIN(test_arena_reset) {
int err;
unsigned arena;
unsigned old_arena;
test_skip_if(should_skip());
test_skip_if(opt_percpu_arena != percpu_arena_disabled);
size_t arena_sz = sizeof(arena);
err = mallctl("arenas.create", (void *)&arena, &arena_sz, NULL, 0);
assert_d_eq(0, err, "Arena creation failed");
err = mallctl("thread.arena", &old_arena, &arena_sz, &arena, arena_sz);
assert_d_eq(0, err, "changing arena failed");
stress_run(&test_arena_reset_main_fn, /* nruns */ 10);
flush_tcache();
char buf[100];
malloc_snprintf(buf, sizeof(buf), "arena.%u.reset", arena);
err = mallctl(buf, NULL, NULL, NULL, 0);
assert_d_eq(0, err, "Couldn't change arena");
do_allocs_frees();
err = mallctl("thread.arena", NULL, NULL, &old_arena, arena_sz);
assert_d_eq(0, err, "changing arena failed");
}
TEST_END
static void
test_fork_main_fn() {
#ifndef _WIN32
pid_t pid = fork();
if (pid == -1) {
test_fail("Fork failure!");
} else if (pid == 0) {
/* Child */
do_allocs_frees();
_exit(0);
} else {
fork_wait_for_child_exit(pid);
do_allocs_frees();
}
#endif
}
TEST_BEGIN(test_fork) {
#ifdef _WIN32
test_skip("No fork on windows");
#endif
test_skip_if(should_skip());
stress_run(&test_fork_main_fn, /* nruns */ 10);
}
TEST_END
static void
test_races_main_fn() {
do_allocs_frees();
}
TEST_BEGIN(test_races) {
test_skip_if(should_skip());
stress_run(&test_races_main_fn, /* nruns */ 400);
assert_zu_lt(0, atomic_load_zu(&push_failure_count, ATOMIC_RELAXED),
"Should have seen some push failures");
assert_zu_lt(0, atomic_load_zu(&pop_attempt_results[0], ATOMIC_RELAXED),
"Should have seen some pop failures");
assert_zu_lt(0, atomic_load_zu(&pop_attempt_results[1], ATOMIC_RELAXED),
"Should have seen some pop successes");
assert_zu_lt(0, atomic_load_zu(&dalloc_zero_slab_count, ATOMIC_RELAXED),
"Expected some frees that didn't empty a slab");
assert_zu_lt(0,
atomic_load_zu(&dalloc_nonzero_slab_count, ATOMIC_RELAXED),
"expected some frees that emptied a slab");
assert_zu_lt(0,
atomic_load_zu(&dalloc_nonempty_list_count, ATOMIC_RELAXED),
"expected some frees that used the empty list");
}
TEST_END
int
main(void) {
return test_no_reentrancy(test_arena_reset, test_races, test_fork);
}

View file

@ -1,10 +0,0 @@
#!/bin/sh
# This value of max_batched_size effectively requires all bins to be batched;
# our page limits are fuzzy, but we bound slab item counts to 2**32, so we'd be
# at multi-gigabyte minimum page sizes.
# The reason for this sort of hacky approach is that we want to
# allocate/deallocate PAGE/2-sized objects (to trigger the "non-empty" ->
# "empty" and "non-empty"-> "full" transitions often, which have special
# handling). But the value of PAGE isn't easily available in test scripts.
export MALLOC_CONF="narenas:2,bin_shards:1-1000000000:3,max_batched_size:1000000000,remote_free_max_batch:1,remote_free_max:4"

View file

@ -1,5 +1,34 @@
#include "test/jemalloc_test.h"
#include "test/fork.h"
#ifndef _WIN32
# include <sys/wait.h>
#endif
#ifndef _WIN32
static void
wait_for_child_exit(int pid) {
int status;
while (true) {
if (waitpid(pid, &status, 0) == -1) {
test_fail("Unexpected waitpid() failure.");
}
if (WIFSIGNALED(status)) {
test_fail(
"Unexpected child termination due to "
"signal %d",
WTERMSIG(status));
break;
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0) {
test_fail("Unexpected child exit value %d",
WEXITSTATUS(status));
}
break;
}
}
}
#endif
TEST_BEGIN(test_fork) {
#ifndef _WIN32
@ -37,7 +66,7 @@ TEST_BEGIN(test_fork) {
/* Child. */
_exit(0);
} else {
fork_wait_for_child_exit(pid);
wait_for_child_exit(pid);
}
#else
test_skip("fork(2) is irrelevant to Windows");
@ -60,7 +89,7 @@ do_fork_thd(void *arg) {
test_fail("Exec failed");
} else {
/* Parent */
fork_wait_for_child_exit(pid);
wait_for_child_exit(pid);
}
return NULL;
}
@ -97,7 +126,7 @@ TEST_BEGIN(test_fork_multithreaded) {
do_test_fork_multithreaded();
_exit(0);
} else {
fork_wait_for_child_exit(pid);
wait_for_child_exit(pid);
}
}
#else