[Lldb-commits] [lldb] c90c594 - [lldb] Improve thread counting in tests (#227312)

via lldb-commits lldb-commits at lists.llvm.org
Wed Sep 30 08:54:08 PDT 2026


Author: Charles Zablit
Date: 2026-09-30T15:53:54Z
New Revision: c90c5946e7d3943e8e4d426f0a321f7560255c16

URL: https://github.com/llvm/llvm-project/commit/c90c5946e7d3943e8e4d426f0a321f7560255c16
DIFF: https://github.com/llvm/llvm-project/commit/c90c5946e7d3943e8e4d426f0a321f7560255c16.diff

LOG: [lldb] Improve thread counting in tests (#227312)

When a test checks how many threads a process created it uses
`process.GetNumThreads()`. This does not account for the fact that the
OS (Windows) can add threads on its own between 2 stops:

On Windows, when an `std::thread` starts, an `ntdll!TppWorkerThread` is
started as well (see [this SO
thread](https://stackoverflow.com/questions/34822072/why-does-windows-10-start-extra-threads-in-my-program)).
That thread raises `CREATE_THREAD_DEBUG_EVENT` only once it first runs,
which can be after the next stop.

This patch introduces `get_threads_in_executable` which only counts the
test's own threads rather than the OS worker threads.

rdar://188700994

Added: 
    

Modified: 
    lldb/packages/Python/lldbsuite/test/lldbutil.py
    lldb/test/API/functionalities/thread/exit_during_step/TestExitDuringStep.py
    lldb/test/API/functionalities/thread/thread_exit/TestThreadExit.py

Removed: 
    


################################################################################
diff  --git a/lldb/packages/Python/lldbsuite/test/lldbutil.py b/lldb/packages/Python/lldbsuite/test/lldbutil.py
index 6c0ae52379de0..d84d7b47a5cab 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbutil.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbutil.py
@@ -964,6 +964,28 @@ def get_one_thread_stopped_at_breakpoint(process, bkpt, require_exactly_one=True
     )
 
 
+def get_threads_in_executable(process):
+    """Returns the threads of process that have a frame in the target's main
+    executable.
+
+    Count these rather than process.GetNumThreads() when a test checks how many
+    threads it created: the OS can add threads of its own between two stops.
+    For example, on Windows, starting a std::thread also starts a thread pool
+    worker thread
+    (https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools).
+    The debugger is only told about that worker once it first runs, which can
+    be after the next stop, so the thread count changes from one stop to the
+    next.
+
+    Match on the module rather than the test's source file: a thread stopped in
+    a runtime library function without debug info may only unwind back to its
+    caller, which is still in the executable.
+    """
+    target = process.GetTarget()
+    exe = target.FindModule(target.GetExecutable())
+    return [t for t in process if any(f.GetModule() == exe for f in t)]
+
+
 def is_thread_crashed(test, thread):
     """In the test suite we dereference a null pointer to simulate a crash. The way this is
     reported depends on the platform."""

diff  --git a/lldb/test/API/functionalities/thread/exit_during_step/TestExitDuringStep.py b/lldb/test/API/functionalities/thread/exit_during_step/TestExitDuringStep.py
index 28fa3c7cfcd07..9822559a73b91 100644
--- a/lldb/test/API/functionalities/thread/exit_during_step/TestExitDuringStep.py
+++ b/lldb/test/API/functionalities/thread/exit_during_step/TestExitDuringStep.py
@@ -76,7 +76,10 @@ def exit_during_step_base(self, step_cmd, step_stop_reason, by_instruction):
         target = self.dbg.GetSelectedTarget()
         process = target.GetProcess()
 
-        num_threads = process.GetNumThreads()
+        # Count only the threads running a.out code: the OS can add a thread
+        # between two stops (see lldbutil.get_threads_in_executable).
+        bp_tids = {t.GetThreadID() for t in lldbutil.get_threads_in_executable(process)}
+        num_threads = len(bp_tids)
         # Make sure we see all three threads
         self.assertGreaterEqual(
             num_threads,
@@ -130,7 +133,7 @@ def exit_during_step_base(self, step_cmd, step_stop_reason, by_instruction):
         self.runCmd("thread list")
 
         # Update the number of threads
-        new_num_threads = process.GetNumThreads()
+        new_num_threads = len(lldbutil.get_threads_in_executable(process))
 
         # Check to see that we reduced the number of threads as expected
         self.assertEqual(
@@ -138,6 +141,10 @@ def exit_during_step_base(self, step_cmd, step_stop_reason, by_instruction):
             num_threads - 1,
             "Number of threads did not reduce by 1 after thread exit.",
         )
+        # The exited thread must be gone from the thread list, not just from
+        # the count: a stale entry must not have an a.out frame.
+        gone = bp_tids - {t.GetThreadID() for t in process}
+        self.assertEqual(len(gone), 1, "The exited thread is still listed.")
 
         self.expect(
             "thread list",

diff  --git a/lldb/test/API/functionalities/thread/thread_exit/TestThreadExit.py b/lldb/test/API/functionalities/thread/thread_exit/TestThreadExit.py
index f1ab48d160495..aa62112935649 100644
--- a/lldb/test/API/functionalities/thread/thread_exit/TestThreadExit.py
+++ b/lldb/test/API/functionalities/thread/thread_exit/TestThreadExit.py
@@ -20,6 +20,10 @@ def setUp(self):
         self.break_3 = line_number("main.cpp", "// Set third breakpoint here")
         self.break_4 = line_number("main.cpp", "// Set fourth breakpoint here")
 
+    @staticmethod
+    def thread_ids(threads):
+        return {t.GetThreadID() for t in threads}
+
     @skipIfWindows  # This is flakey on Windows: llvm.org/pr38373
     def test(self):
         """Test thread exit handling."""
@@ -68,8 +72,8 @@ def test(self):
         )
         self.assertIsNotNone(stopped_thread, "Process is not stopped at breakpoint 1")
 
-        # Get the number of threads
-        num_threads = process.GetNumThreads()
+        bp1_tids = self.thread_ids(lldbutil.get_threads_in_executable(process))
+        num_threads = len(bp1_tids)
         self.assertGreaterEqual(
             num_threads,
             2,
@@ -82,14 +86,17 @@ def test(self):
             process, bp2_id
         )
         self.assertIsNotNone(stopped_thread, "Process is not stopped at breakpoint 2")
+        thread1_tid = stopped_thread.GetThreadID()
 
         # Update the number of threads
-        new_num_threads = process.GetNumThreads()
+        bp2_tids = self.thread_ids(lldbutil.get_threads_in_executable(process))
+        new_num_threads = len(bp2_tids)
         self.assertEqual(
             new_num_threads,
             num_threads + 1,
             "Number of expected threads did not increase by 1 at bp 2.",
         )
+        (thread2_tid,) = bp2_tids - bp1_tids
 
         # Run to the third breakpoint
         self.runCmd("continue")
@@ -98,8 +105,16 @@ def test(self):
         )
         self.assertIsNotNone(stopped_thread, "Process is not stopped at breakpoint 3")
 
+        # thread1 was joined. Check it is gone from the thread list, not just
+        # from the count: a stale entry must not have an a.out frame.
+        self.assertNotIn(
+            thread1_tid,
+            self.thread_ids(process),
+            "Exited thread1 is still listed at bp 3.",
+        )
+
         # Update the number of threads
-        new_num_threads = process.GetNumThreads()
+        new_num_threads = len(lldbutil.get_threads_in_executable(process))
         self.assertEqual(
             new_num_threads,
             num_threads,
@@ -113,8 +128,14 @@ def test(self):
         )
         self.assertIsNotNone(stopped_thread, "Process is not stopped at breakpoint 4")
 
+        self.assertNotIn(
+            thread2_tid,
+            self.thread_ids(process),
+            "Exited thread2 is still listed at bp 4.",
+        )
+
         # Update the number of threads
-        new_num_threads = process.GetNumThreads()
+        new_num_threads = len(lldbutil.get_threads_in_executable(process))
         self.assertEqual(
             new_num_threads,
             num_threads - 1,


        


More information about the lldb-commits mailing list