diff --git a/Makefile.in b/Makefile.in index c7bab1a2..c161bccf 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/include/jemalloc/internal/test_hooks.h b/include/jemalloc/internal/test_hooks.h index 1b56c717..a4c9d6c0 100644 --- a/include/jemalloc/internal/test_hooks.h +++ b/include/jemalloc/internal/test_hooks.h @@ -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) \ diff --git a/src/tcache.c b/src/tcache.c index 5f6b8239..87a44522 100644 --- a/src/tcache.c +++ b/src/tcache.c @@ -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. */ diff --git a/src/test_hooks.c b/src/test_hooks.c index 0f4c4cf7..3fc8534e 100644 --- a/src/test_hooks.c +++ b/src/test_hooks.c @@ -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; diff --git a/test/stress/tsd_reentrant.c b/test/stress/tsd_reentrant.c new file mode 100644 index 00000000..220f1c3e --- /dev/null +++ b/test/stress/tsd_reentrant.c @@ -0,0 +1,140 @@ +#include "test/jemalloc_test.h" + +#ifndef _WIN32 +#include +#include +#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 +}