Skip to content

Commit 7164104

Browse files
[libc] ensure tls dtors are called in main thread (#133641)
This is a part of allocator patch since I want to make sure the TLS for allocators are correctly handled. TLS dtors are not invoked on exit previously. This departures from major libc implementations. This PR fixes the issue by aligning the behavior with bionic. https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/bionic/exit.cpp I believe the finalization order is also consistent with glibc now: On main thread exiting: - pthread_key dtors are not called (unless exiting with `pthread_exit`) - `__cxa` based tls dtors are called - `::__cxa_atexit` and `::atexit` dtors are called - `.fini` dtors are called ![image](https://github.com/user-attachments/assets/737c4845-cab6-47a9-aa00-32997be141bd)
1 parent 3f4d116 commit 7164104

File tree

6 files changed

+97
-1
lines changed

6 files changed

+97
-1
lines changed

libc/src/__support/threads/thread.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ void call_atexit_callbacks(ThreadAttributes *attrib) {
163163
}
164164
}
165165

166+
extern "C" void __cxa_thread_finalize() { call_atexit_callbacks(self.attrib); }
167+
166168
} // namespace internal
167169

168170
cpp::optional<unsigned int> new_tss_key(TSSDtor *dtor) {

libc/src/__support/threads/thread.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct alignas(STACK_ALIGNMENT) ThreadAttributes {
110110
ThreadAtExitCallbackMgr *atexit_callback_mgr;
111111
void *platform_data;
112112

113-
constexpr ThreadAttributes()
113+
LIBC_INLINE constexpr ThreadAttributes()
114114
: detach_state(uint32_t(DetachState::DETACHED)), stack(nullptr),
115115
stacksize(0), guardsize(0), tls(0), tls_size(0), owned_stack(false),
116116
tid(-1), style(ThreadStyle::POSIX), retval(),

libc/src/stdlib/exit.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,17 @@ namespace LIBC_NAMESPACE_DECL {
1515

1616
extern "C" void __cxa_finalize(void *);
1717

18+
// exit needs to clean up TLS and call associated destructors.
19+
// TODO: Strictly speaking, it is not valid to call exit in overlay mode
20+
// as we have no way to ensure system libc will call the TLS destructors.
21+
// We should run exit related tests in hermetic mode but this is currently
22+
// blocked by https://github.com/llvm/llvm-project/issues/133925.
23+
extern "C" [[gnu::weak]] void __cxa_thread_finalize();
24+
25+
// TODO: use recursive mutex to protect this routine.
1826
[[noreturn]] LLVM_LIBC_FUNCTION(void, exit, (int status)) {
27+
if (__cxa_thread_finalize)
28+
__cxa_thread_finalize();
1929
__cxa_finalize(nullptr);
2030
internal::exit(status);
2131
}

libc/test/integration/src/__support/threads/CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,24 @@ add_integration_test(
2525
DEPENDS
2626
libc.src.__support.threads.thread
2727
)
28+
29+
add_integration_test(
30+
main_exit_test
31+
SUITE
32+
libc-support-threads-integration-tests
33+
SRCS
34+
main_exit_test.cpp
35+
DEPENDS
36+
libc.src.__support.threads.thread
37+
)
38+
39+
add_integration_test(
40+
double_exit_test
41+
SUITE
42+
libc-support-threads-integration-tests
43+
SRCS
44+
double_exit_test.cpp
45+
DEPENDS
46+
libc.src.__support.threads.thread
47+
libc.src.stdlib.exit
48+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//===-- Test handling of thread local data --------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/__support/threads/thread.h"
10+
#include "src/stdlib/exit.h"
11+
#include "test/IntegrationTest/test.h"
12+
13+
extern "C" {
14+
[[gnu::weak]]
15+
void *__dso_handle = nullptr;
16+
int __cxa_thread_atexit_impl(void (*func)(void *), void *arg, void *dso);
17+
}
18+
19+
int call_num = 0;
20+
21+
[[gnu::destructor]]
22+
void check() {
23+
// This destructor should be called only once.
24+
if (call_num != 1)
25+
__builtin_trap();
26+
}
27+
28+
TEST_MAIN() {
29+
__cxa_thread_atexit_impl([](void *) { LIBC_NAMESPACE::exit(0); }, nullptr,
30+
__dso_handle);
31+
__cxa_thread_atexit_impl([](void *) { ++call_num; }, nullptr, __dso_handle);
32+
LIBC_NAMESPACE::exit(1);
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===-- Test handling of thread local data --------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/__support/threads/thread.h"
10+
#include "test/IntegrationTest/test.h"
11+
12+
bool called = false;
13+
14+
extern "C" {
15+
[[gnu::weak]]
16+
void *__dso_handle = nullptr;
17+
int __cxa_thread_atexit_impl(void (*func)(void *), void *arg, void *dso);
18+
}
19+
20+
[[gnu::destructor]]
21+
void destructor() {
22+
if (!called)
23+
__builtin_trap();
24+
}
25+
26+
TEST_MAIN() {
27+
__cxa_thread_atexit_impl([](void *) { called = true; }, nullptr,
28+
__dso_handle);
29+
return 0;
30+
}

0 commit comments

Comments
 (0)