[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