[Lldb-commits] [lldb] [lldb] Fix TestThreadExit.py flakiness (PR #190976)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Fri Apr 17 12:46:37 PDT 2026
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/190976
>From 5f7ef10cde2c1f682852fa3634c6ae9ef2f297b2 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Wed, 8 Apr 2026 14:46:21 +0100
Subject: [PATCH 1/2] [lldb] Fix TestThreadExit.py flakiness
When `pthread_join` returns, the target thread signals its internal
semaphore, but the underlying Mach thread hasn't been removed from the
task yet with `thread_terminate`.
The flakiness is the result of the debugger stopping the process halts
the dying thread mid-termination. There is no Mach API to distinguish a
dying thread from a live one: it appears as TH_STATE_STOPPED like any
other suspended thread.
This PR adds a helper (`wait_for_thread_cleanup`) that polls
task_threads() to ensure the Mach thread is actually gone before the
breakpoint. Since there might be other tests that are affected by this
race, I put it in a common location so it can be shared.
---
.../Python/lldbsuite/test/make/mach_thread.h | 26 +++++++
.../thread/thread_exit/main.cpp | 76 +++++++++----------
2 files changed, 63 insertions(+), 39 deletions(-)
create mode 100644 lldb/packages/Python/lldbsuite/test/make/mach_thread.h
diff --git a/lldb/packages/Python/lldbsuite/test/make/mach_thread.h b/lldb/packages/Python/lldbsuite/test/make/mach_thread.h
new file mode 100644
index 0000000000000..3c00811a6e79c
--- /dev/null
+++ b/lldb/packages/Python/lldbsuite/test/make/mach_thread.h
@@ -0,0 +1,26 @@
+#ifdef __APPLE__
+#include <mach/mach.h>
+#include <thread>
+#endif
+
+// After join returns, the underlying Mach thread may not have been terminated
+// yet (pthread_join uses a semaphore that is signaled before the thread calls
+// thread_terminate). If the debugger stops the process in this window it will
+// freeze the dying thread, making it appear still alive in task_threads(). Poll
+// until the kernel-level thread count matches.
+void wait_for_thread_cleanup(unsigned int expected) {
+#ifdef __APPLE__
+ while (true) {
+ thread_array_t thread_list;
+ mach_msg_type_number_t count;
+ kern_return_t kr = task_threads(mach_task_self(), &thread_list, &count);
+ if (kr == KERN_SUCCESS) {
+ vm_deallocate(mach_task_self(), (vm_address_t)thread_list,
+ count * sizeof(thread_t));
+ if (count <= expected)
+ break;
+ }
+ std::this_thread::yield();
+ }
+#endif
+}
diff --git a/lldb/test/API/functionalities/thread/thread_exit/main.cpp b/lldb/test/API/functionalities/thread/thread_exit/main.cpp
index 6c60a40bb66b0..162fec6e296cc 100644
--- a/lldb/test/API/functionalities/thread/thread_exit/main.cpp
+++ b/lldb/test/API/functionalities/thread/thread_exit/main.cpp
@@ -1,5 +1,6 @@
// This test verifies the correct handling of child thread exits.
+#include "mach_thread.h"
#include "pseudo_barrier.h"
#include <thread>
@@ -7,59 +8,56 @@ pseudo_barrier_t g_barrier1;
pseudo_barrier_t g_barrier2;
pseudo_barrier_t g_barrier3;
-void *
-thread1 ()
-{
- // Synchronize with the main thread.
- pseudo_barrier_wait(g_barrier1);
+void *thread1() {
+ // Synchronize with the main thread.
+ pseudo_barrier_wait(g_barrier1);
- // Synchronize with the main thread and thread2.
- pseudo_barrier_wait(g_barrier2);
+ // Synchronize with the main thread and thread2.
+ pseudo_barrier_wait(g_barrier2);
- // Return
- return NULL; // Set second breakpoint here
+ // Return
+ return NULL; // Set second breakpoint here
}
-void *
-thread2 ()
-{
- // Synchronize with thread1 and the main thread.
- pseudo_barrier_wait(g_barrier2);
+void *thread2() {
+ // Synchronize with thread1 and the main thread.
+ pseudo_barrier_wait(g_barrier2);
- // Synchronize with the main thread.
- pseudo_barrier_wait(g_barrier3);
+ // Synchronize with the main thread.
+ pseudo_barrier_wait(g_barrier3);
- // Return
- return NULL;
+ // Return
+ return NULL;
}
-int main ()
-{
- pseudo_barrier_init(g_barrier1, 2);
- pseudo_barrier_init(g_barrier2, 3);
- pseudo_barrier_init(g_barrier3, 2);
+int main() {
+ pseudo_barrier_init(g_barrier1, 2);
+ pseudo_barrier_init(g_barrier2, 3);
+ pseudo_barrier_init(g_barrier3, 2);
- // Create a thread.
- std::thread thread_1(thread1);
+ // Create a thread.
+ std::thread thread_1(thread1);
- // Wait for thread1 to start.
- pseudo_barrier_wait(g_barrier1);
+ // Wait for thread1 to start.
+ pseudo_barrier_wait(g_barrier1);
- // Create another thread.
- std::thread thread_2(thread2); // Set first breakpoint here
+ // Create another thread.
+ std::thread thread_2(thread2); // Set first breakpoint here
- // Wait for thread2 to start.
- pseudo_barrier_wait(g_barrier2);
+ // Wait for thread2 to start.
+ pseudo_barrier_wait(g_barrier2);
- // Wait for the first thread to finish
- thread_1.join();
+ // Wait for the first thread to finish
+ thread_1.join();
+ wait_for_thread_cleanup(2);
- // Synchronize with the remaining thread
- int dummy = 47; // Set third breakpoint here
- pseudo_barrier_wait(g_barrier3);
+ // Synchronize with the remaining thread
+ int dummy = 47; // Set third breakpoint here
+ pseudo_barrier_wait(g_barrier3);
- // Wait for the second thread to finish
- thread_2.join();
+ // Wait for the second thread to finish
+ thread_2.join();
+ wait_for_thread_cleanup(1);
- return 0; // Set fourth breakpoint here
+ return 0; // Set fourth breakpoint here
}
>From 6f8a6f50677ae4436dc7206b9fe9f2732dd8b21b Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 17 Apr 2026 12:46:21 -0700
Subject: [PATCH 2/2] Implement Jim's suggestion
---
.../Python/lldbsuite/test/make/mach_thread.h | 36 +++++++++++++------
.../thread/thread_exit/main.cpp | 6 ++--
2 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/make/mach_thread.h b/lldb/packages/Python/lldbsuite/test/make/mach_thread.h
index 3c00811a6e79c..3106b0017a581 100644
--- a/lldb/packages/Python/lldbsuite/test/make/mach_thread.h
+++ b/lldb/packages/Python/lldbsuite/test/make/mach_thread.h
@@ -1,26 +1,42 @@
#ifdef __APPLE__
#include <mach/mach.h>
+#include <pthread.h>
#include <thread>
-#endif
+
+// Get the Mach thread port for a std::thread. Must be called before join(),
+// because the native_handle is invalidated after join.
+mach_port_t get_mach_thread(std::thread &t) {
+ return pthread_mach_thread_np(t.native_handle());
+}
// After join returns, the underlying Mach thread may not have been terminated
// yet (pthread_join uses a semaphore that is signaled before the thread calls
// thread_terminate). If the debugger stops the process in this window it will
// freeze the dying thread, making it appear still alive in task_threads(). Poll
-// until the kernel-level thread count matches.
-void wait_for_thread_cleanup(unsigned int expected) {
-#ifdef __APPLE__
+// until the specific Mach thread is gone from the task's thread list.
+void wait_for_thread_cleanup(mach_port_t mach_thread) {
while (true) {
thread_array_t thread_list;
mach_msg_type_number_t count;
kern_return_t kr = task_threads(mach_task_self(), &thread_list, &count);
- if (kr == KERN_SUCCESS) {
- vm_deallocate(mach_task_self(), (vm_address_t)thread_list,
- count * sizeof(thread_t));
- if (count <= expected)
- break;
+ if (kr != KERN_SUCCESS)
+ break;
+ bool found = false;
+ for (mach_msg_type_number_t i = 0; i < count; i++) {
+ if (thread_list[i] == mach_thread)
+ found = true;
+ mach_port_deallocate(mach_task_self(), thread_list[i]);
}
+ vm_deallocate(mach_task_self(), (vm_address_t)thread_list,
+ count * sizeof(thread_t));
+ if (!found)
+ break;
std::this_thread::yield();
}
-#endif
}
+#else
+#include <thread>
+typedef unsigned int mach_port_t;
+inline mach_port_t get_mach_thread(std::thread &t) { return 0; }
+inline void wait_for_thread_cleanup(mach_port_t) {}
+#endif
diff --git a/lldb/test/API/functionalities/thread/thread_exit/main.cpp b/lldb/test/API/functionalities/thread/thread_exit/main.cpp
index 162fec6e296cc..6a351d4ebaa7d 100644
--- a/lldb/test/API/functionalities/thread/thread_exit/main.cpp
+++ b/lldb/test/API/functionalities/thread/thread_exit/main.cpp
@@ -48,16 +48,18 @@ int main() {
pseudo_barrier_wait(g_barrier2);
// Wait for the first thread to finish
+ mach_port_t mach_thread_1 = get_mach_thread(thread_1);
thread_1.join();
- wait_for_thread_cleanup(2);
+ wait_for_thread_cleanup(mach_thread_1);
// Synchronize with the remaining thread
int dummy = 47; // Set third breakpoint here
pseudo_barrier_wait(g_barrier3);
// Wait for the second thread to finish
+ mach_port_t mach_thread_2 = get_mach_thread(thread_2);
thread_2.join();
- wait_for_thread_cleanup(1);
+ wait_for_thread_cleanup(mach_thread_2);
return 0; // Set fourth breakpoint here
}
More information about the lldb-commits
mailing list