diff --git a/src/snmalloc/global/threadalloc.h b/src/snmalloc/global/threadalloc.h index 77994ad45..601411aaf 100644 --- a/src/snmalloc/global/threadalloc.h +++ b/src/snmalloc/global/threadalloc.h @@ -180,8 +180,15 @@ namespace snmalloc /** * Used to give correct signature to teardown required by atexit. + * If [[gnu::destructor]] is available, we use the attribute to register + * the finalisation statically. In VERY rare cases, dynamic registration + * can trigger deadlocks. */ - static void pthread_cleanup_main_thread() +# if __has_attribute(destructor) + [[gnu::destructor]] +# endif + static void + pthread_cleanup_main_thread() { ThreadAlloc::teardown(); } @@ -198,7 +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) 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..ef98256f8 --- /dev/null +++ b/src/test/func/multi_atexit/multi_atexit.cc @@ -0,0 +1,42 @@ +#ifndef __has_feature +# define __has_feature(x) 0 +#endif + +#if defined(__linux__) && !__has_feature(address_sanitizer) && \ + !__has_feature(thread_sanitizer) && \ + !__has_feature(undefined_behavior_sanitizer) && \ + !defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \ + !defined(__SANITIZE_UNDEFINED__) +# define RUN_TEST +#endif + +#ifdef RUN_TEST +# include +# 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); +} + +extern "C" void free(void* p) +{ + if (snmalloc::is_owned(p)) + return snmalloc::libc::free(p); + // otherwise, just leak the memory +} + +#endif + +int main() +{ +#ifdef RUN_TEST + for (int i = 0; i < 8192; ++i) + atexit(do_nothing); +#endif +} \ No newline at end of file