From 43e09b757790b8b9eddf4720f5ea8e5887f1e2c5 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sun, 21 Sep 2025 16:32:46 +0100 Subject: [PATCH] Fix race condition in test_external_inspection thread status tests The tests test_thread_status_detection and test_thread_status_gil_detection had a race condition where the test could sample thread status between when the sleeper thread sends its "ready" message and when it actually calls time.sleep(). This caused intermittent test failures where the sleeper thread would show as running (status=0) instead of idle (status=1 or 2). The fix moves the thread status collection inside the retry loop and specifically waits for the expected thread states before proceeding with assertions. The retry loop now continues until: - The sleeper thread shows as idle (status=1 for CPU mode, status=2 for GIL mode) - The busy thread shows as running (status=0) - Both thread IDs are found in the status collection This ensures the test waits for threads to settle into their expected states before making assertions, eliminating the race condition. --- Lib/test/test_external_inspection.py | 44 +++++++++++++++------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 2f8f5f0e169339..01720457e61f5c 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -1751,14 +1751,23 @@ def busy(): break attempts = 10 + statuses = {} try: unwinder = RemoteUnwinder(p.pid, all_threads=True, mode=PROFILING_MODE_CPU, skip_non_matching_threads=False) for _ in range(attempts): traces = unwinder.get_stack_trace() - # Check if any thread is running - if any(thread_info.status == 0 for interpreter_info in traces - for thread_info in interpreter_info.threads): + # Find threads and their statuses + statuses = {} + for interpreter_info in traces: + for thread_info in interpreter_info.threads: + statuses[thread_info.thread_id] = thread_info.status + + # Check if sleeper thread is idle and busy thread is running + if (sleeper_tid in statuses and + busy_tid in statuses and + statuses[sleeper_tid] == 1 and + statuses[busy_tid] == 0): break time.sleep(0.5) # Give a bit of time to let threads settle except PermissionError: @@ -1766,13 +1775,6 @@ def busy(): "Insufficient permissions to read the stack trace" ) - - # Find threads and their statuses - statuses = {} - for interpreter_info in traces: - for thread_info in interpreter_info.threads: - statuses[thread_info.thread_id] = thread_info.status - self.assertIsNotNone(sleeper_tid, "Sleeper thread id not received") self.assertIsNotNone(busy_tid, "Busy thread id not received") self.assertIn(sleeper_tid, statuses, "Sleeper tid not found in sampled threads") @@ -1861,14 +1863,23 @@ def busy(): break attempts = 10 + statuses = {} try: unwinder = RemoteUnwinder(p.pid, all_threads=True, mode=PROFILING_MODE_GIL, skip_non_matching_threads=False) for _ in range(attempts): traces = unwinder.get_stack_trace() - # Check if any thread is running - if any(thread_info.status == 0 for interpreter_info in traces - for thread_info in interpreter_info.threads): + # Find threads and their statuses + statuses = {} + for interpreter_info in traces: + for thread_info in interpreter_info.threads: + statuses[thread_info.thread_id] = thread_info.status + + # Check if sleeper thread is idle (status 2 for GIL mode) and busy thread is running + if (sleeper_tid in statuses and + busy_tid in statuses and + statuses[sleeper_tid] == 2 and + statuses[busy_tid] == 0): break time.sleep(0.5) # Give a bit of time to let threads settle except PermissionError: @@ -1876,13 +1887,6 @@ def busy(): "Insufficient permissions to read the stack trace" ) - - # Find threads and their statuses - statuses = {} - for interpreter_info in traces: - for thread_info in interpreter_info.threads: - statuses[thread_info.thread_id] = thread_info.status - self.assertIsNotNone(sleeper_tid, "Sleeper thread id not received") self.assertIsNotNone(busy_tid, "Busy thread id not received") self.assertIn(sleeper_tid, statuses, "Sleeper tid not found in sampled threads")