Add TSD reentrant stress test

This commit is contained in:
Farid Zakaria 2026-07-02 13:00:25 -07:00 committed by Slobodan Predolac
parent 7ce8b9165d
commit 7c34a482c1
5 changed files with 149 additions and 1 deletions

View file

@ -367,7 +367,8 @@ TESTS_ANALYZE := $(srcroot)test/analyze/prof_bias.c \
TESTS_STRESS := $(srcroot)test/stress/fill_flush.c \
$(srcroot)test/stress/large_microbench.c \
$(srcroot)test/stress/mallctl.c \
$(srcroot)test/stress/microbench.c
$(srcroot)test/stress/microbench.c \
$(srcroot)test/stress/tsd_reentrant.c
ifeq (@enable_cxx@, 1)
TESTS_STRESS_CPP := $(srcroot)test/stress/cpp/microbench.cpp
else

View file

@ -4,6 +4,7 @@
extern JEMALLOC_EXPORT void (*test_hooks_arena_new_hook)(void);
extern JEMALLOC_EXPORT void (*test_hooks_libc_hook)(void);
extern JEMALLOC_EXPORT void (*test_hooks_safety_check_abort)(const char *);
extern JEMALLOC_EXPORT void (*test_hooks_tsd_bootstrap_hook)(void);
#if defined(JEMALLOC_JET) || defined(JEMALLOC_UNIT_TEST)
# define JEMALLOC_TEST_HOOK(fn, hook) \

View file

@ -1051,6 +1051,9 @@ tsd_tcache_enabled_data_init(tsd_t *tsd) {
*/
tcache_default_settings_init(tsd_tcache_slowp_get(tsd));
tsd_slow_update(tsd);
if (test_hooks_tsd_bootstrap_hook != NULL) {
test_hooks_tsd_bootstrap_hook();
}
if (opt_tcache) {
/* Trigger tcache init. */

View file

@ -13,3 +13,6 @@ void (*test_hooks_libc_hook)(void) = NULL;
JEMALLOC_EXPORT
void (*test_hooks_safety_check_abort)(const char *) = NULL;
JEMALLOC_EXPORT
void (*test_hooks_tsd_bootstrap_hook)(void) = NULL;

140
test/stress/tsd_reentrant.c Normal file
View file

@ -0,0 +1,140 @@
#include "test/jemalloc_test.h"
#ifndef _WIN32
#include <signal.h>
#include <unistd.h>
#endif
#ifndef _WIN32
extern void (*je_test_hooks_tsd_bootstrap_hook)(void);
static atomic_zu_t handler_allocs;
static atomic_b_t crash_reported;
static bool hook_ran;
static volatile sig_atomic_t in_reentrant_signal_alloc;
static void *volatile signal_alloc;
static void
on_crash(int signo) {
#define WRITE_LITERAL(s) (void)write(STDERR_FILENO, s, sizeof(s) - 1)
if (atomic_exchange_b(&crash_reported, true, ATOMIC_RELAXED)) {
_exit(128 + signo);
}
WRITE_LITERAL("\n*** tsd_reentrant reproduced allocator crash ***\n");
if (signo == SIGSEGV) {
WRITE_LITERAL("signal: SIGSEGV\n");
} else if (signo == SIGBUS) {
WRITE_LITERAL("signal: SIGBUS\n");
}
WRITE_LITERAL("inside SIGUSR1 handler malloc: ");
if (in_reentrant_signal_alloc) {
WRITE_LITERAL("yes\n");
} else {
WRITE_LITERAL("no\n");
}
WRITE_LITERAL("This matches recursive allocation during TSD/tcache "
"bootstrap.\n");
#undef WRITE_LITERAL
_exit(128 + signo);
}
static void
on_signal(int signo) {
(void)signo;
in_reentrant_signal_alloc = 1;
/*
* This intentionally models asynchronous interruption that allocates.
* jemalloc does not promise that arbitrary signal-handler allocation is
* safe, but the allocator should keep its own bootstrap state
* internally consistent if such reentrancy occurs.
*/
void *p = malloc(16);
if (p != NULL) {
signal_alloc = p;
atomic_load_add_store_zu(&handler_allocs, 1);
}
in_reentrant_signal_alloc = 0;
}
static void
tsd_bootstrap_signal_hook(void) {
hook_ran = true;
je_test_hooks_tsd_bootstrap_hook = NULL;
/*
* The hook only selects the bootstrap window. The recursive allocation
* still enters through the process signal path and public malloc().
*/
raise(SIGUSR1);
}
static void *
worker_start(void *arg) {
(void)arg;
void *p = malloc(16);
expect_ptr_not_null(p, "Unexpected malloc() failure");
if (p != NULL) {
memset(p, 0xa5, 16);
free(p);
}
je_test_hooks_tsd_bootstrap_hook = NULL;
return NULL;
}
static void
init_signal_handler(void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_signal;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
expect_d_eq(sigaction(SIGUSR1, &sa, NULL), 0,
"Unexpected sigaction() failure");
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_crash;
sigemptyset(&sa.sa_mask);
expect_d_eq(sigaction(SIGSEGV, &sa, NULL), 0,
"Unexpected sigaction() failure");
expect_d_eq(sigaction(SIGBUS, &sa, NULL), 0,
"Unexpected sigaction() failure");
}
TEST_BEGIN(test_tsd_bootstrap_signal_reentrant) {
thd_t worker;
init_signal_handler();
atomic_store_b(&crash_reported, false, ATOMIC_RELEASE);
atomic_store_zu(&handler_allocs, 0, ATOMIC_RELEASE);
hook_ran = false;
je_test_hooks_tsd_bootstrap_hook = tsd_bootstrap_signal_hook;
thd_create(&worker, worker_start, NULL);
thd_join(worker, NULL);
je_test_hooks_tsd_bootstrap_hook = NULL;
expect_true(hook_ran, "TSD bootstrap hook should have executed");
expect_zu_eq(atomic_load_zu(&handler_allocs, ATOMIC_ACQUIRE), 1,
"Signal handler should have allocated");
}
TEST_END
#endif
int
main(void) {
#ifdef _WIN32
test_skip("Signals are unavailable");
return test_status_skip;
#else
return test_no_reentrancy(test_tsd_bootstrap_signal_reentrant);
#endif
}