[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