From 9ddfb4bdf68ec346b444ee74b4abaf525714657a Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 16:36:26 -0400 Subject: [PATCH 01/19] handle reentrancy during initialization --- src/snmalloc/global/threadalloc.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index 77994ad45..a6c7f0137 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -210,6 +210,10 @@ namespace snmalloc */ static void register_clean_up() { + thread_local bool called = false; + if (called) + return; + called = true; Singleton p_key; // We need to set a non-null value, so that the destructor is called, // we never look at the value. From b77fdabf5d4f2709c0ddac7a794b5648e5b923b5 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 17:13:35 -0400 Subject: [PATCH 02/19] use finialization list if possible --- src/snmalloc/global/threadalloc.h | 5 +++++ src/test/func/multi_atexit/multi_atexit.cc | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/test/func/multi_atexit/multi_atexit.cc diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index a6c7f0137..4a8478de5 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -181,6 +181,9 @@ namespace snmalloc /** * Used to give correct signature to teardown required by atexit. */ +#if __has_attribute(destructor) + [[gnu::destructor]] +#endif static void pthread_cleanup_main_thread() { ThreadAlloc::teardown(); @@ -198,7 +201,9 @@ namespace snmalloc // run at least once. If the main thread exits with `pthread_exit` then // it will be called twice but this case is already handled because other // destructors can cause the per-thread allocator to be recreated. +#if !__has_attribute(destructor) atexit(&pthread_cleanup_main_thread); +#endif } public: diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc new file mode 100644 index 000000000..1e44156e4 --- /dev/null +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -0,0 +1,21 @@ +#ifdef __linux__ +# include +# include + +void do_nothing() {} + +extern "C" void * calloc(size_t num, size_t size) { + return snmalloc::libc::calloc(num, size); +} + +extern "C" void free(void * p) { + return snmalloc::libc::free(p); +} +#endif + +int main() { +#ifdef __linux__ + for (int i = 0; i < 8192; ++i) + atexit(do_nothing); +#endif +} \ No newline at end of file From 7bccb1f5b5b70b7cdd5d9ee4c1b008181333a835 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 17:36:22 -0400 Subject: [PATCH 03/19] override more symbols --- src/test/func/multi_atexit/multi_atexit.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc index 1e44156e4..3b50694a7 100644 --- a/src/test/func/multi_atexit/multi_atexit.cc +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -3,14 +3,9 @@ # include void do_nothing() {} +#define SNMALLOC_NAME_MANGLE(X) X +#include "snmalloc/override/malloc.cc" -extern "C" void * calloc(size_t num, size_t size) { - return snmalloc::libc::calloc(num, size); -} - -extern "C" void free(void * p) { - return snmalloc::libc::free(p); -} #endif int main() { From ed459fd4a7cd26e70f15ab0a900982a86fbccd27 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 17:58:51 -0400 Subject: [PATCH 04/19] revert --- src/test/func/multi_atexit/multi_atexit.cc | 14 ++++++++++---- src/test/func/multi_tls_key/multi_tls_key.cc | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/test/func/multi_tls_key/multi_tls_key.cc diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc index 3b50694a7..fd13f0a32 100644 --- a/src/test/func/multi_atexit/multi_atexit.cc +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -1,15 +1,21 @@ -#ifdef __linux__ +#if defined(__linux__) && !__has_feature(address_sanitizer) # include # include void do_nothing() {} -#define SNMALLOC_NAME_MANGLE(X) X -#include "snmalloc/override/malloc.cc" +extern "C" void * calloc(size_t num, size_t size) { + return snmalloc::libc::calloc(num, size); +} + +extern "C" void free(void * p) { + if (snmalloc::is_owned(p)) + return snmalloc::libc::free(p); +} #endif int main() { -#ifdef __linux__ +#if defined(__linux__) && !__has_feature(address_sanitizer) for (int i = 0; i < 8192; ++i) atexit(do_nothing); #endif diff --git a/src/test/func/multi_tls_key/multi_tls_key.cc b/src/test/func/multi_tls_key/multi_tls_key.cc new file mode 100644 index 000000000..47f7670fe --- /dev/null +++ b/src/test/func/multi_tls_key/multi_tls_key.cc @@ -0,0 +1,17 @@ +#ifdef __linux__ +# include +# include + +void do_nothing() {} + +# define SNMALLOC_NAME_MANGLE(X) X +# include "snmalloc/override/malloc.cc" + +#endif + +int main() { +#ifdef __linux__ + for (int i = 0; i < 8192; ++i) + atexit(do_nothing); +#endif +} \ No newline at end of file From 71c2853c7df6210b7404cef8a84dd1bd997dcf0b Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 17:59:54 -0400 Subject: [PATCH 05/19] revert --- src/snmalloc/global/threadalloc.h | 3 ++- src/test/func/multi_tls_key/multi_tls_key.cc | 17 ----------------- 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 src/test/func/multi_tls_key/multi_tls_key.cc diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index 4a8478de5..d352cf930 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -218,7 +218,8 @@ namespace snmalloc thread_local bool called = false; if (called) return; - called = true; + write(STDOUT_FILENO, "register_clean_up\n", 18); + // called = true; Singleton p_key; // We need to set a non-null value, so that the destructor is called, // we never look at the value. diff --git a/src/test/func/multi_tls_key/multi_tls_key.cc b/src/test/func/multi_tls_key/multi_tls_key.cc deleted file mode 100644 index 47f7670fe..000000000 --- a/src/test/func/multi_tls_key/multi_tls_key.cc +++ /dev/null @@ -1,17 +0,0 @@ -#ifdef __linux__ -# include -# include - -void do_nothing() {} - -# define SNMALLOC_NAME_MANGLE(X) X -# include "snmalloc/override/malloc.cc" - -#endif - -int main() { -#ifdef __linux__ - for (int i = 0; i < 8192; ++i) - atexit(do_nothing); -#endif -} \ No newline at end of file From 3a7ae8de74e95daf279e253b603a7bc9b0df5525 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 18:42:23 -0400 Subject: [PATCH 06/19] fix up --- src/snmalloc/global/threadalloc.h | 8 +++----- src/test/func/multi_atexit/multi_atexit.cc | 5 +++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index d352cf930..bd248674e 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -180,6 +180,9 @@ namespace snmalloc /** * Used to give correct signature to teardown required by atexit. + * If [[gnu::destructor]] is available, we use the attribute to register + * the finalization statically. In VERY rare cases, dynamic registration + * can trigger deadlocks. */ #if __has_attribute(destructor) [[gnu::destructor]] @@ -215,11 +218,6 @@ namespace snmalloc */ static void register_clean_up() { - thread_local bool called = false; - if (called) - return; - write(STDOUT_FILENO, "register_clean_up\n", 18); - // called = true; Singleton p_key; // We need to set a non-null value, so that the destructor is called, // we never look at the value. diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc index fd13f0a32..bb3b510da 100644 --- a/src/test/func/multi_atexit/multi_atexit.cc +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -3,6 +3,10 @@ # include void do_nothing() {} + +// We only selectively override these functions. Otherwise, malloc may be called +// before atexit triggers the first initialization attempt. + extern "C" void * calloc(size_t num, size_t size) { return snmalloc::libc::calloc(num, size); } @@ -10,6 +14,7 @@ extern "C" void * calloc(size_t num, size_t size) { extern "C" void free(void * p) { if (snmalloc::is_owned(p)) return snmalloc::libc::free(p); + // otherwise, just leak the memory } #endif From e3833d90674311db29d3576d9ac4ca9501256dba Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 18:44:05 -0400 Subject: [PATCH 07/19] fix up --- src/snmalloc/global/threadalloc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index bd248674e..df607decf 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -181,7 +181,7 @@ namespace snmalloc /** * Used to give correct signature to teardown required by atexit. * If [[gnu::destructor]] is available, we use the attribute to register - * the finalization statically. In VERY rare cases, dynamic registration + * the finalisation statically. In VERY rare cases, dynamic registration * can trigger deadlocks. */ #if __has_attribute(destructor) From 37eac1a445066c7f8b0e83f7a51d368f0957c0ef Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 18:46:26 -0400 Subject: [PATCH 08/19] apply format patch --- src/snmalloc/global/threadalloc.h | 11 ++++++----- src/test/func/multi_atexit/multi_atexit.cc | 9 ++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index df607decf..601411aaf 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -184,10 +184,11 @@ namespace snmalloc * the finalisation statically. In VERY rare cases, dynamic registration * can trigger deadlocks. */ -#if __has_attribute(destructor) +# if __has_attribute(destructor) [[gnu::destructor]] -#endif - static void pthread_cleanup_main_thread() +# endif + static void + pthread_cleanup_main_thread() { ThreadAlloc::teardown(); } @@ -204,9 +205,9 @@ namespace snmalloc // run at least once. If the main thread exits with `pthread_exit` then // it will be called twice but this case is already handled because other // destructors can cause the per-thread allocator to be recreated. -#if !__has_attribute(destructor) +# if !__has_attribute(destructor) atexit(&pthread_cleanup_main_thread); -#endif +# endif } public: diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc index bb3b510da..7d7c62a99 100644 --- a/src/test/func/multi_atexit/multi_atexit.cc +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -7,11 +7,13 @@ void do_nothing() {} // We only selectively override these functions. Otherwise, malloc may be called // before atexit triggers the first initialization attempt. -extern "C" void * calloc(size_t num, size_t size) { +extern "C" void* calloc(size_t num, size_t size) +{ return snmalloc::libc::calloc(num, size); } -extern "C" void free(void * p) { +extern "C" void free(void* p) +{ if (snmalloc::is_owned(p)) return snmalloc::libc::free(p); // otherwise, just leak the memory @@ -19,7 +21,8 @@ extern "C" void free(void * p) { #endif -int main() { +int main() +{ #if defined(__linux__) && !__has_feature(address_sanitizer) for (int i = 0; i < 8192; ++i) atexit(do_nothing); From 5e5b4ef928e4f4833d70e39b5276e8f4303f9dc2 Mon Sep 17 00:00:00 2001 From: Schrodinger ZHU Yifan Date: Wed, 26 Mar 2025 18:51:34 -0400 Subject: [PATCH 09/19] fix --- src/test/func/multi_atexit/multi_atexit.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc index 7d7c62a99..924f7c24e 100644 --- a/src/test/func/multi_atexit/multi_atexit.cc +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -1,4 +1,13 @@ -#if defined(__linux__) && !__has_feature(address_sanitizer) +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#if defined(__linux__) && !__has_feature(address_sanitizer) && \ + !defined(__SANITIZE_ADDRESS__) +# define RUN_TEST +#endif + +#ifdef RUN_TEST # include # include @@ -23,7 +32,7 @@ extern "C" void free(void* p) int main() { -#if defined(__linux__) && !__has_feature(address_sanitizer) +#ifdef RUN_TEST for (int i = 0; i < 8192; ++i) atexit(do_nothing); #endif From 11a11830a04c457707eef85e852294733a568dd3 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Thu, 27 Mar 2025 16:42:37 +0000 Subject: [PATCH 10/19] Add test for reentrancy of C++ destructors and allocation --- .../multi_threadatexit/multi_threadatexit.cc | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/test/func/multi_threadatexit/multi_threadatexit.cc diff --git a/src/test/func/multi_threadatexit/multi_threadatexit.cc b/src/test/func/multi_threadatexit/multi_threadatexit.cc new file mode 100644 index 000000000..cb9405bd5 --- /dev/null +++ b/src/test/func/multi_threadatexit/multi_threadatexit.cc @@ -0,0 +1,55 @@ +#include + +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#if defined(__linux__) && !__has_feature(address_sanitizer) && \ + !defined(__SANITIZE_ADDRESS__) +# define RUN_TEST +#endif + +#ifdef RUN_TEST +# include +# include + +template +void thread_destruct() +{ + snmalloc::message("thread_destruct<{}, {}> start", N, M); + static thread_local snmalloc::OnDestruct destruct{[](){ + snmalloc::message("thread_destruct<{}, {}> destructor", N, M); + }}; + snmalloc::message("thread_destruct<{}, {}> end", N, M); + + if constexpr (N > M + 1) + { + // destructor + thread_destruct(); + thread_destruct<(M + N)/2, M>(); + } +} + +// We only selectively override these functions. Otherwise, malloc may be called +// before atexit triggers the first initialization attempt. + +extern "C" void* calloc(size_t num, size_t size) +{ + snmalloc::message("calloc({}, {})", num, size); + return snmalloc::libc::calloc(num, size); +} + +extern "C" void free(void* p) +{ + snmalloc::message("free({})", p); + if (snmalloc::is_owned(p)) + return snmalloc::libc::free(p); + // otherwise, just leak the memory +} + +#endif + +int main() +{ + std::thread(thread_destruct<1000>).join(); +} \ No newline at end of file From dec3becf8530b4b2df4d712872de03496237c10a Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Thu, 27 Mar 2025 16:43:04 +0000 Subject: [PATCH 11/19] Add test for reentrancy of setspecific --- .../multi_setspecific/multi_setspecific.cc | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/test/func/multi_setspecific/multi_setspecific.cc diff --git a/src/test/func/multi_setspecific/multi_setspecific.cc b/src/test/func/multi_setspecific/multi_setspecific.cc new file mode 100644 index 000000000..ed414527d --- /dev/null +++ b/src/test/func/multi_setspecific/multi_setspecific.cc @@ -0,0 +1,75 @@ +#include +#include +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#if defined(__linux__) && !__has_feature(address_sanitizer) && \ + !defined(__SANITIZE_ADDRESS__) +# define RUN_TEST +#endif + +#ifdef RUN_TEST +# include +# include + +// A key in the second "second level" block of the pthread key table. +// First second level block is statically allocated. +// This is be 33. +pthread_key_t key; + +void thread_setspecific() +{ + // If the following line is uncommented then the test will pass. + // free(calloc(1, 1)); + pthread_setspecific(key, (void*)1); +} + +// We only selectively override these functions. Otherwise, malloc may be called +// before atexit triggers the first initialization attempt. + +extern "C" void* calloc(size_t num, size_t size) +{ + snmalloc::message("calloc({}, {})", num, size); + return snmalloc::libc::calloc(num, size); +} + +extern "C" void free(void* p) +{ + snmalloc::message("free({})", p); + // Just leak it + if (snmalloc::is_owned(p)) + return snmalloc::libc::free(p); +} + +#endif + +void callback(void*) +{ + snmalloc::message("callback"); +} + +int main() +{ + // The first 32 keys are statically allocated, so we need to create 33 keys + // to create a key for which pthread_setspecific will call the calloc. + for (size_t i = 0; i < 33; i++) + { + pthread_key_create(&key, callback); + } + + // The first calloc occurs here, after the first [0, 32] keys have been created + // thus snmalloc will choose the key 33, `key` contains the key `32` and snmalloc `33`. + // Both of these keys are not in the statically allocated part of the pthread key space. + std::thread(thread_setspecific).join(); + + // There should be a single allocator that can be extracted. + if (snmalloc::AllocPool::extract() == nullptr) + { + // The thread has not torn down its allocator. + snmalloc::report_fatal_error("Teardown of thread allocator has not occurred."); + return 1; + } + + return 0; +} \ No newline at end of file From b68362bb1750a049a076e88a40a753b21f3ac871 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Thu, 27 Mar 2025 16:43:30 +0000 Subject: [PATCH 12/19] Add new mode for directly calling __cxa_thread_atexit_impl --- CMakeLists.txt | 6 +-- src/snmalloc/global/threadalloc.h | 40 ++++++++++++++++++- .../thread_alloc_external.cc | 3 ++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91cdbe9e1..54c6fef06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,13 +48,13 @@ if (NOT SNMALLOC_HEADER_ONLY_LIBRARY) if (${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD) set(SNMALLOC_CLEANUP_DEFAULT THREAD_CLEANUP) elseif (UNIX AND NOT APPLE) - set(SNMALLOC_CLEANUP_DEFAULT PTHREAD_DESTRUCTORS) + set(SNMALLOC_CLEANUP_DEFAULT CXX11_THREAD_ATEXIT_DIRECT) else () set(SNMALLOC_CLEANUP_DEFAULT CXX11_DESTRUCTORS) endif() # Specify the thread cleanup mechanism to use. - set(SNMALLOC_CLEANUP ${SNMALLOC_CLEANUP_DEFAULT} CACHE STRING "The mechanism that snmalloc will use for thread destructors. Valid options are: CXX11_DESTRUCTORS (use C++11 destructors, may depend on the C++ runtime library), PTHREAD_DESTRUCTORS (use pthreads, may interact badly with C++ on some platforms, such as macOS) THREAD_CLEANUP (depend on an explicit call to _malloc_thread_cleanup on thread exit, supported by FreeBSD's threading implementation and possibly elsewhere)") - set_property(CACHE SNMALLOC_CLEANUP PROPERTY STRINGS THREAD_CLEANUP PTHREAD_DESTRUCTORS CXX11_DESTRUCTORS) + set(SNMALLOC_CLEANUP ${SNMALLOC_CLEANUP_DEFAULT} CACHE STRING "The mechanism that snmalloc will use for thread destructors. Valid options are: CXX11_DESTRUCTORS (use C++11 destructors, may depend on the C++ runtime library), CXX11_THREAD_ATEXIT_DIRECT (use the glibc call for libstdc++ directly), PTHREAD_DESTRUCTORS (use pthreads, may interact badly with C++ on some platforms, such as macOS) THREAD_CLEANUP (depend on an explicit call to _malloc_thread_cleanup on thread exit, supported by FreeBSD's threading implementation and possibly elsewhere)") + set_property(CACHE SNMALLOC_CLEANUP PROPERTY STRINGS THREAD_CLEANUP PTHREAD_DESTRUCTORS CXX11_THREAD_ATEXIT_DIRECT CXX11_DESTRUCTORS) set(SNMALLOC_STATIC_LIBRARY_PREFIX "sn_" CACHE STRING "Static library function prefix") set(SNMALLOC_COMPILER_SUPPORT_IPO FALSE) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index 601411aaf..e05ac0883 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -21,6 +21,15 @@ # endif #endif +#if defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT) +# if defined(SNMALLOC_THREAD_TEARDOWN_DEFINED) +# error At most one out of method of thread teardown can be specified. +# endif +# define SNMALLOC_THREAD_TEARDOWN_DEFINED +extern "C" int __cxa_thread_atexit_impl(void (func)(void*), void*, void*); +extern "C" void* __dso_handle; +#endif + #if !defined(SNMALLOC_THREAD_TEARDOWN_DEFINED) # define SNMALLOC_USE_CXX_THREAD_DESTRUCTORS #endif @@ -56,6 +65,7 @@ namespace snmalloc class CheckInitPthread; class CheckInitCXX; class CheckInitThreadCleanup; + class CheckInitCXXAtExitDirect; /** * Holds the thread local state for the allocator. The state is constant @@ -159,8 +169,10 @@ namespace snmalloc }; # ifdef SNMALLOC_USE_PTHREAD_DESTRUCTORS using CheckInit = CheckInitPthread; -# elif defined(SNMALLOC_USE_CXX_THREAD_DESTRUCTORS) +# elif defined(SNMALLOC_USE_CXX11_THREAD_DESTRUCTORS) using CheckInit = CheckInitCXX; +# elif defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT) + using CheckInit = CheckInitCXXAtExitDirect; # else using CheckInit = CheckInitThreadCleanup; # endif @@ -229,7 +241,31 @@ namespace snmalloc # endif } }; -# elif defined(SNMALLOC_USE_CXX_THREAD_DESTRUCTORS) +# elif defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT) + class CheckInitCXXAtExitDirect + : public ThreadAlloc::CheckInitBase + { + static void cleanup(void*) + { + ThreadAlloc::teardown(); + } + + public: + /** + * This function is called by each thread once it starts using the + * thread local allocator. + * + * This implementation depends on the libstdc++ requirement on libc + * that provides the functionality to register a destructor for the + * thread locals. This allows to not require either pthread or + * C++ std lib. + */ + static void register_clean_up() + { + __cxa_thread_atexit_impl(cleanup, nullptr, &__dso_handle); + } + }; +# elif defined(SNMALLOC_USE_CXX11_THREAD_DESTRUCTORS) class CheckInitCXX : public ThreadAlloc::CheckInitBase { public: diff --git a/src/test/func/thread_alloc_external/thread_alloc_external.cc b/src/test/func/thread_alloc_external/thread_alloc_external.cc index bbc9227d4..07d41c4b0 100644 --- a/src/test/func/thread_alloc_external/thread_alloc_external.cc +++ b/src/test/func/thread_alloc_external/thread_alloc_external.cc @@ -1,6 +1,9 @@ #ifdef SNMALLOC_USE_PTHREAD_DESTRUCTORS # undef SNMALLOC_USE_PTHREAD_DESTRUCTORS #endif +#ifdef SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT +# undef SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT +#endif #include #include From a301ea8ad2128c67ceae4a3f2744b7c9cc391fe1 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 15:15:47 +0100 Subject: [PATCH 13/19] CI fixes --- src/snmalloc/global/threadalloc.h | 2 +- .../multi_setspecific/multi_setspecific.cc | 25 ++++++++++++------- .../multi_threadatexit/multi_threadatexit.cc | 9 +++---- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index e05ac0883..e7a4d0ace 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -26,7 +26,7 @@ # error At most one out of method of thread teardown can be specified. # endif # define SNMALLOC_THREAD_TEARDOWN_DEFINED -extern "C" int __cxa_thread_atexit_impl(void (func)(void*), void*, void*); +extern "C" int __cxa_thread_atexit_impl(void(func)(void*), void*, void*); extern "C" void* __dso_handle; #endif diff --git a/src/test/func/multi_setspecific/multi_setspecific.cc b/src/test/func/multi_setspecific/multi_setspecific.cc index ed414527d..7979de556 100644 --- a/src/test/func/multi_setspecific/multi_setspecific.cc +++ b/src/test/func/multi_setspecific/multi_setspecific.cc @@ -1,5 +1,3 @@ -#include -#include #ifndef __has_feature # define __has_feature(x) 0 #endif @@ -10,8 +8,10 @@ #endif #ifdef RUN_TEST +# include # include # include +# include // A key in the second "second level" block of the pthread key table. // First second level block is statically allocated. @@ -42,8 +42,6 @@ extern "C" void free(void* p) return snmalloc::libc::free(p); } -#endif - void callback(void*) { snmalloc::message("callback"); @@ -58,18 +56,27 @@ int main() pthread_key_create(&key, callback); } - // The first calloc occurs here, after the first [0, 32] keys have been created - // thus snmalloc will choose the key 33, `key` contains the key `32` and snmalloc `33`. - // Both of these keys are not in the statically allocated part of the pthread key space. + // The first calloc occurs here, after the first [0, 32] keys have been + // created thus snmalloc will choose the key 33, `key` contains the key `32` + // and snmalloc `33`. Both of these keys are not in the statically allocated + // part of the pthread key space. std::thread(thread_setspecific).join(); // There should be a single allocator that can be extracted. if (snmalloc::AllocPool::extract() == nullptr) { // The thread has not torn down its allocator. - snmalloc::report_fatal_error("Teardown of thread allocator has not occurred."); + snmalloc::report_fatal_error( + "Teardown of thread allocator has not occurred."); return 1; } return 0; -} \ No newline at end of file +} +#else +int main() +{ + // This test is not run, but it is used to check that the code compiles. + return 0; +} +#endif diff --git a/src/test/func/multi_threadatexit/multi_threadatexit.cc b/src/test/func/multi_threadatexit/multi_threadatexit.cc index cb9405bd5..e5c70264f 100644 --- a/src/test/func/multi_threadatexit/multi_threadatexit.cc +++ b/src/test/func/multi_threadatexit/multi_threadatexit.cc @@ -17,16 +17,15 @@ template void thread_destruct() { snmalloc::message("thread_destruct<{}, {}> start", N, M); - static thread_local snmalloc::OnDestruct destruct{[](){ - snmalloc::message("thread_destruct<{}, {}> destructor", N, M); - }}; + static thread_local snmalloc::OnDestruct destruct{ + []() { snmalloc::message("thread_destruct<{}, {}> destructor", N, M); }}; snmalloc::message("thread_destruct<{}, {}> end", N, M); if constexpr (N > M + 1) { // destructor - thread_destruct(); - thread_destruct<(M + N)/2, M>(); + thread_destruct(); + thread_destruct<(M + N) / 2, M>(); } } From 6afddd6cf56b1c84470f28bc82e2e7784aa00f50 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 15:25:38 +0100 Subject: [PATCH 14/19] CI --- src/test/func/multi_threadatexit/multi_threadatexit.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/func/multi_threadatexit/multi_threadatexit.cc b/src/test/func/multi_threadatexit/multi_threadatexit.cc index e5c70264f..f238d1da6 100644 --- a/src/test/func/multi_threadatexit/multi_threadatexit.cc +++ b/src/test/func/multi_threadatexit/multi_threadatexit.cc @@ -46,9 +46,13 @@ extern "C" void free(void* p) // otherwise, just leak the memory } -#endif - int main() { std::thread(thread_destruct<1000>).join(); -} \ No newline at end of file +} +#else +int main() +{ + return 0; +} +#endif From cd0a7739dfc8971760064ec6cd0cb202ac01c1fa Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 15:52:52 +0100 Subject: [PATCH 15/19] Fix ifdef --- src/snmalloc/global/threadalloc.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index e7a4d0ace..e49c6d5c5 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -30,8 +30,16 @@ extern "C" int __cxa_thread_atexit_impl(void(func)(void*), void*, void*); extern "C" void* __dso_handle; #endif +#if defined(SNMALLOC_USE_CXX11_DESTRUCTORS) +# if defined(SNMALLOC_THREAD_TEARDOWN_DEFINED) +# error At most one out of method of thread teardown can be specified. +# endif +# define SNMALLOC_THREAD_TEARDOWN_DEFINED +#endif + #if !defined(SNMALLOC_THREAD_TEARDOWN_DEFINED) -# define SNMALLOC_USE_CXX_THREAD_DESTRUCTORS +// Default to C++11 destructors if nothing has been specified. +# define SNMALLOC_USE_CXX11_DESTRUCTORS #endif extern "C" void _malloc_thread_cleanup(); @@ -169,7 +177,7 @@ namespace snmalloc }; # ifdef SNMALLOC_USE_PTHREAD_DESTRUCTORS using CheckInit = CheckInitPthread; -# elif defined(SNMALLOC_USE_CXX11_THREAD_DESTRUCTORS) +# elif defined(SNMALLOC_USE_CXX11_DESTRUCTORS) using CheckInit = CheckInitCXX; # elif defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT) using CheckInit = CheckInitCXXAtExitDirect; @@ -265,7 +273,7 @@ namespace snmalloc __cxa_thread_atexit_impl(cleanup, nullptr, &__dso_handle); } }; -# elif defined(SNMALLOC_USE_CXX11_THREAD_DESTRUCTORS) +# elif defined(SNMALLOC_USE_CXX11_DESTRUCTORS) class CheckInitCXX : public ThreadAlloc::CheckInitBase { public: From 33a81c6005535428b32622287dc16a9f3084f7f3 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 16:06:13 +0100 Subject: [PATCH 16/19] Fix ifdef --- src/test/func/thread_alloc_external/thread_alloc_external.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/func/thread_alloc_external/thread_alloc_external.cc b/src/test/func/thread_alloc_external/thread_alloc_external.cc index 07d41c4b0..8869d0f43 100644 --- a/src/test/func/thread_alloc_external/thread_alloc_external.cc +++ b/src/test/func/thread_alloc_external/thread_alloc_external.cc @@ -4,6 +4,9 @@ #ifdef SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT # undef SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT #endif +#ifdef SNMALLOC_USE_CXX11_DESTRUCTORS +# undef SNMALLOC_USE_CXX11_DESTRUCTORS +#endif #include #include From a69607cc4036369dab3c4a5f96707c9305e7d56d Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 16:26:58 +0100 Subject: [PATCH 17/19] Fix sanitizers --- src/test/func/multi_setspecific/multi_setspecific.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/func/multi_setspecific/multi_setspecific.cc b/src/test/func/multi_setspecific/multi_setspecific.cc index 7979de556..940c043c6 100644 --- a/src/test/func/multi_setspecific/multi_setspecific.cc +++ b/src/test/func/multi_setspecific/multi_setspecific.cc @@ -2,8 +2,13 @@ # define __has_feature(x) 0 #endif +// These test partially override the libc malloc/free functions to test +// interesting corner cases. This breaks the sanitizers as they will be +// partially overridden. So we disable the tests if any of the sanitizers are +// enabled. #if defined(__linux__) && !__has_feature(address_sanitizer) && \ - !defined(__SANITIZE_ADDRESS__) + !defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) \ + && !defined(SNMALLOC_THREAD_SANITIZER_ENABLED) # define RUN_TEST #endif From 95d2235e8f5192cdae3aa7444bcb22371c2ca495 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 16:33:27 +0100 Subject: [PATCH 18/19] CF --- src/test/func/multi_setspecific/multi_setspecific.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/func/multi_setspecific/multi_setspecific.cc b/src/test/func/multi_setspecific/multi_setspecific.cc index 940c043c6..486116fe5 100644 --- a/src/test/func/multi_setspecific/multi_setspecific.cc +++ b/src/test/func/multi_setspecific/multi_setspecific.cc @@ -7,8 +7,8 @@ // partially overridden. So we disable the tests if any of the sanitizers are // enabled. #if defined(__linux__) && !__has_feature(address_sanitizer) && \ - !defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) \ - && !defined(SNMALLOC_THREAD_SANITIZER_ENABLED) + !defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \ + !defined(SNMALLOC_THREAD_SANITIZER_ENABLED) # define RUN_TEST #endif From 84f883514040ffaaf57f1fa50cac01c186261aff Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 28 Apr 2025 17:13:38 +0100 Subject: [PATCH 19/19] Fix sanitizers --- src/test/func/multi_atexit/multi_atexit.cc | 7 ++++++- src/test/func/multi_threadatexit/multi_threadatexit.cc | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/test/func/multi_atexit/multi_atexit.cc b/src/test/func/multi_atexit/multi_atexit.cc index 924f7c24e..e4f5ed327 100644 --- a/src/test/func/multi_atexit/multi_atexit.cc +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -2,8 +2,13 @@ # define __has_feature(x) 0 #endif +// These test partially override the libc malloc/free functions to test +// interesting corner cases. This breaks the sanitizers as they will be +// partially overridden. So we disable the tests if any of the sanitizers are +// enabled. #if defined(__linux__) && !__has_feature(address_sanitizer) && \ - !defined(__SANITIZE_ADDRESS__) + !defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \ + !defined(SNMALLOC_THREAD_SANITIZER_ENABLED) # define RUN_TEST #endif diff --git a/src/test/func/multi_threadatexit/multi_threadatexit.cc b/src/test/func/multi_threadatexit/multi_threadatexit.cc index f238d1da6..85f918f93 100644 --- a/src/test/func/multi_threadatexit/multi_threadatexit.cc +++ b/src/test/func/multi_threadatexit/multi_threadatexit.cc @@ -4,8 +4,13 @@ # define __has_feature(x) 0 #endif +// These test partially override the libc malloc/free functions to test +// interesting corner cases. This breaks the sanitizers as they will be +// partially overridden. So we disable the tests if any of the sanitizers are +// enabled. #if defined(__linux__) && !__has_feature(address_sanitizer) && \ - !defined(__SANITIZE_ADDRESS__) + !defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \ + !defined(SNMALLOC_THREAD_SANITIZER_ENABLED) # define RUN_TEST #endif