Skip to content

Commit 0064c01

Browse files
Improving handling first allocation being for TLS (#767)
* handle reentrancy during initialization * use finialization list if possible * Add test for reentrancy of C++ destructors and allocation * Add test for reentrancy of setspecific * Add new mode for directly calling __cxa_thread_atexit_impl --------- Co-authored-by: Schrodinger ZHU Yifan <[email protected]>
1 parent a63f0c1 commit 0064c01

File tree

6 files changed

+260
-7
lines changed

6 files changed

+260
-7
lines changed

CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ if (NOT SNMALLOC_HEADER_ONLY_LIBRARY)
4848
if (${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD)
4949
set(SNMALLOC_CLEANUP_DEFAULT THREAD_CLEANUP)
5050
elseif (UNIX AND NOT APPLE)
51-
set(SNMALLOC_CLEANUP_DEFAULT PTHREAD_DESTRUCTORS)
51+
set(SNMALLOC_CLEANUP_DEFAULT CXX11_THREAD_ATEXIT_DIRECT)
5252
else ()
5353
set(SNMALLOC_CLEANUP_DEFAULT CXX11_DESTRUCTORS)
5454
endif()
5555
# Specify the thread cleanup mechanism to use.
56-
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)")
57-
set_property(CACHE SNMALLOC_CLEANUP PROPERTY STRINGS THREAD_CLEANUP PTHREAD_DESTRUCTORS CXX11_DESTRUCTORS)
56+
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)")
57+
set_property(CACHE SNMALLOC_CLEANUP PROPERTY STRINGS THREAD_CLEANUP PTHREAD_DESTRUCTORS CXX11_THREAD_ATEXIT_DIRECT CXX11_DESTRUCTORS)
5858

5959
set(SNMALLOC_STATIC_LIBRARY_PREFIX "sn_" CACHE STRING "Static library function prefix")
6060
set(SNMALLOC_COMPILER_SUPPORT_IPO FALSE)

src/snmalloc/global/threadalloc.h

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,25 @@
2121
# endif
2222
#endif
2323

24+
#if defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT)
25+
# if defined(SNMALLOC_THREAD_TEARDOWN_DEFINED)
26+
# error At most one out of method of thread teardown can be specified.
27+
# endif
28+
# define SNMALLOC_THREAD_TEARDOWN_DEFINED
29+
extern "C" int __cxa_thread_atexit_impl(void(func)(void*), void*, void*);
30+
extern "C" void* __dso_handle;
31+
#endif
32+
33+
#if defined(SNMALLOC_USE_CXX11_DESTRUCTORS)
34+
# if defined(SNMALLOC_THREAD_TEARDOWN_DEFINED)
35+
# error At most one out of method of thread teardown can be specified.
36+
# endif
37+
# define SNMALLOC_THREAD_TEARDOWN_DEFINED
38+
#endif
39+
2440
#if !defined(SNMALLOC_THREAD_TEARDOWN_DEFINED)
25-
# define SNMALLOC_USE_CXX_THREAD_DESTRUCTORS
41+
// Default to C++11 destructors if nothing has been specified.
42+
# define SNMALLOC_USE_CXX11_DESTRUCTORS
2643
#endif
2744
extern "C" void _malloc_thread_cleanup();
2845

@@ -56,6 +73,7 @@ namespace snmalloc
5673
class CheckInitPthread;
5774
class CheckInitCXX;
5875
class CheckInitThreadCleanup;
76+
class CheckInitCXXAtExitDirect;
5977

6078
/**
6179
* Holds the thread local state for the allocator. The state is constant
@@ -159,8 +177,10 @@ namespace snmalloc
159177
};
160178
# ifdef SNMALLOC_USE_PTHREAD_DESTRUCTORS
161179
using CheckInit = CheckInitPthread;
162-
# elif defined(SNMALLOC_USE_CXX_THREAD_DESTRUCTORS)
180+
# elif defined(SNMALLOC_USE_CXX11_DESTRUCTORS)
163181
using CheckInit = CheckInitCXX;
182+
# elif defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT)
183+
using CheckInit = CheckInitCXXAtExitDirect;
164184
# else
165185
using CheckInit = CheckInitThreadCleanup;
166186
# endif
@@ -180,8 +200,15 @@ namespace snmalloc
180200

181201
/**
182202
* Used to give correct signature to teardown required by atexit.
203+
* If [[gnu::destructor]] is available, we use the attribute to register
204+
* the finalisation statically. In VERY rare cases, dynamic registration
205+
* can trigger deadlocks.
183206
*/
184-
static void pthread_cleanup_main_thread()
207+
# if __has_attribute(destructor)
208+
[[gnu::destructor]]
209+
# endif
210+
static void
211+
pthread_cleanup_main_thread()
185212
{
186213
ThreadAlloc::teardown();
187214
}
@@ -198,7 +225,9 @@ namespace snmalloc
198225
// run at least once. If the main thread exits with `pthread_exit` then
199226
// it will be called twice but this case is already handled because other
200227
// destructors can cause the per-thread allocator to be recreated.
228+
# if !__has_attribute(destructor)
201229
atexit(&pthread_cleanup_main_thread);
230+
# endif
202231
}
203232

204233
public:
@@ -220,7 +249,31 @@ namespace snmalloc
220249
# endif
221250
}
222251
};
223-
# elif defined(SNMALLOC_USE_CXX_THREAD_DESTRUCTORS)
252+
# elif defined(SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT)
253+
class CheckInitCXXAtExitDirect
254+
: public ThreadAlloc::CheckInitBase<CheckInitCXXAtExitDirect>
255+
{
256+
static void cleanup(void*)
257+
{
258+
ThreadAlloc::teardown();
259+
}
260+
261+
public:
262+
/**
263+
* This function is called by each thread once it starts using the
264+
* thread local allocator.
265+
*
266+
* This implementation depends on the libstdc++ requirement on libc
267+
* that provides the functionality to register a destructor for the
268+
* thread locals. This allows to not require either pthread or
269+
* C++ std lib.
270+
*/
271+
static void register_clean_up()
272+
{
273+
__cxa_thread_atexit_impl(cleanup, nullptr, &__dso_handle);
274+
}
275+
};
276+
# elif defined(SNMALLOC_USE_CXX11_DESTRUCTORS)
224277
class CheckInitCXX : public ThreadAlloc::CheckInitBase<CheckInitCXX>
225278
{
226279
public:
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#ifndef __has_feature
2+
# define __has_feature(x) 0
3+
#endif
4+
5+
// These test partially override the libc malloc/free functions to test
6+
// interesting corner cases. This breaks the sanitizers as they will be
7+
// partially overridden. So we disable the tests if any of the sanitizers are
8+
// enabled.
9+
#if defined(__linux__) && !__has_feature(address_sanitizer) && \
10+
!defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \
11+
!defined(SNMALLOC_THREAD_SANITIZER_ENABLED)
12+
# define RUN_TEST
13+
#endif
14+
15+
#ifdef RUN_TEST
16+
# include <snmalloc/snmalloc.h>
17+
# include <stdlib.h>
18+
19+
void do_nothing() {}
20+
21+
// We only selectively override these functions. Otherwise, malloc may be called
22+
// before atexit triggers the first initialization attempt.
23+
24+
extern "C" void* calloc(size_t num, size_t size)
25+
{
26+
return snmalloc::libc::calloc(num, size);
27+
}
28+
29+
extern "C" void free(void* p)
30+
{
31+
if (snmalloc::is_owned(p))
32+
return snmalloc::libc::free(p);
33+
// otherwise, just leak the memory
34+
}
35+
36+
#endif
37+
38+
int main()
39+
{
40+
#ifdef RUN_TEST
41+
for (int i = 0; i < 8192; ++i)
42+
atexit(do_nothing);
43+
#endif
44+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#ifndef __has_feature
2+
# define __has_feature(x) 0
3+
#endif
4+
5+
// These test partially override the libc malloc/free functions to test
6+
// interesting corner cases. This breaks the sanitizers as they will be
7+
// partially overridden. So we disable the tests if any of the sanitizers are
8+
// enabled.
9+
#if defined(__linux__) && !__has_feature(address_sanitizer) && \
10+
!defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \
11+
!defined(SNMALLOC_THREAD_SANITIZER_ENABLED)
12+
# define RUN_TEST
13+
#endif
14+
15+
#ifdef RUN_TEST
16+
# include <array>
17+
# include <snmalloc/snmalloc.h>
18+
# include <stdlib.h>
19+
# include <thread>
20+
21+
// A key in the second "second level" block of the pthread key table.
22+
// First second level block is statically allocated.
23+
// This is be 33.
24+
pthread_key_t key;
25+
26+
void thread_setspecific()
27+
{
28+
// If the following line is uncommented then the test will pass.
29+
// free(calloc(1, 1));
30+
pthread_setspecific(key, (void*)1);
31+
}
32+
33+
// We only selectively override these functions. Otherwise, malloc may be called
34+
// before atexit triggers the first initialization attempt.
35+
36+
extern "C" void* calloc(size_t num, size_t size)
37+
{
38+
snmalloc::message("calloc({}, {})", num, size);
39+
return snmalloc::libc::calloc(num, size);
40+
}
41+
42+
extern "C" void free(void* p)
43+
{
44+
snmalloc::message("free({})", p);
45+
// Just leak it
46+
if (snmalloc::is_owned(p))
47+
return snmalloc::libc::free(p);
48+
}
49+
50+
void callback(void*)
51+
{
52+
snmalloc::message("callback");
53+
}
54+
55+
int main()
56+
{
57+
// The first 32 keys are statically allocated, so we need to create 33 keys
58+
// to create a key for which pthread_setspecific will call the calloc.
59+
for (size_t i = 0; i < 33; i++)
60+
{
61+
pthread_key_create(&key, callback);
62+
}
63+
64+
// The first calloc occurs here, after the first [0, 32] keys have been
65+
// created thus snmalloc will choose the key 33, `key` contains the key `32`
66+
// and snmalloc `33`. Both of these keys are not in the statically allocated
67+
// part of the pthread key space.
68+
std::thread(thread_setspecific).join();
69+
70+
// There should be a single allocator that can be extracted.
71+
if (snmalloc::AllocPool<snmalloc::Config>::extract() == nullptr)
72+
{
73+
// The thread has not torn down its allocator.
74+
snmalloc::report_fatal_error(
75+
"Teardown of thread allocator has not occurred.");
76+
return 1;
77+
}
78+
79+
return 0;
80+
}
81+
#else
82+
int main()
83+
{
84+
// This test is not run, but it is used to check that the code compiles.
85+
return 0;
86+
}
87+
#endif
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include <thread>
2+
3+
#ifndef __has_feature
4+
# define __has_feature(x) 0
5+
#endif
6+
7+
// These test partially override the libc malloc/free functions to test
8+
// interesting corner cases. This breaks the sanitizers as they will be
9+
// partially overridden. So we disable the tests if any of the sanitizers are
10+
// enabled.
11+
#if defined(__linux__) && !__has_feature(address_sanitizer) && \
12+
!defined(__SANITIZE_ADDRESS__) && !defined(__SANITIZE_THREAD__) && \
13+
!defined(SNMALLOC_THREAD_SANITIZER_ENABLED)
14+
# define RUN_TEST
15+
#endif
16+
17+
#ifdef RUN_TEST
18+
# include <snmalloc/snmalloc.h>
19+
# include <stdlib.h>
20+
21+
template<size_t N, size_t M = 0>
22+
void thread_destruct()
23+
{
24+
snmalloc::message("thread_destruct<{}, {}> start", N, M);
25+
static thread_local snmalloc::OnDestruct destruct{
26+
[]() { snmalloc::message("thread_destruct<{}, {}> destructor", N, M); }};
27+
snmalloc::message("thread_destruct<{}, {}> end", N, M);
28+
29+
if constexpr (N > M + 1)
30+
{
31+
// destructor
32+
thread_destruct<N, (M + N) / 2>();
33+
thread_destruct<(M + N) / 2, M>();
34+
}
35+
}
36+
37+
// We only selectively override these functions. Otherwise, malloc may be called
38+
// before atexit triggers the first initialization attempt.
39+
40+
extern "C" void* calloc(size_t num, size_t size)
41+
{
42+
snmalloc::message("calloc({}, {})", num, size);
43+
return snmalloc::libc::calloc(num, size);
44+
}
45+
46+
extern "C" void free(void* p)
47+
{
48+
snmalloc::message("free({})", p);
49+
if (snmalloc::is_owned(p))
50+
return snmalloc::libc::free(p);
51+
// otherwise, just leak the memory
52+
}
53+
54+
int main()
55+
{
56+
std::thread(thread_destruct<1000>).join();
57+
}
58+
#else
59+
int main()
60+
{
61+
return 0;
62+
}
63+
#endif

src/test/func/thread_alloc_external/thread_alloc_external.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#ifdef SNMALLOC_USE_PTHREAD_DESTRUCTORS
22
# undef SNMALLOC_USE_PTHREAD_DESTRUCTORS
33
#endif
4+
#ifdef SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT
5+
# undef SNMALLOC_USE_CXX11_THREAD_ATEXIT_DIRECT
6+
#endif
7+
#ifdef SNMALLOC_USE_CXX11_DESTRUCTORS
8+
# undef SNMALLOC_USE_CXX11_DESTRUCTORS
9+
#endif
410

511
#include <new>
612
#include <snmalloc/snmalloc_core.h>

0 commit comments

Comments
 (0)