Skip to content

Commit 9df477c

Browse files
authored
gh-138709: Fix race condition in test_external_inspection (#139209)
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.
1 parent 080faf2 commit 9df477c

File tree

1 file changed

+24
-20
lines changed

1 file changed

+24
-20
lines changed

Lib/test/test_external_inspection.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,28 +1751,30 @@ def busy():
17511751
break
17521752

17531753
attempts = 10
1754+
statuses = {}
17541755
try:
17551756
unwinder = RemoteUnwinder(p.pid, all_threads=True, mode=PROFILING_MODE_CPU,
17561757
skip_non_matching_threads=False)
17571758
for _ in range(attempts):
17581759
traces = unwinder.get_stack_trace()
1759-
# Check if any thread is running
1760-
if any(thread_info.status == 0 for interpreter_info in traces
1761-
for thread_info in interpreter_info.threads):
1760+
# Find threads and their statuses
1761+
statuses = {}
1762+
for interpreter_info in traces:
1763+
for thread_info in interpreter_info.threads:
1764+
statuses[thread_info.thread_id] = thread_info.status
1765+
1766+
# Check if sleeper thread is idle and busy thread is running
1767+
if (sleeper_tid in statuses and
1768+
busy_tid in statuses and
1769+
statuses[sleeper_tid] == 1 and
1770+
statuses[busy_tid] == 0):
17621771
break
17631772
time.sleep(0.5) # Give a bit of time to let threads settle
17641773
except PermissionError:
17651774
self.skipTest(
17661775
"Insufficient permissions to read the stack trace"
17671776
)
17681777

1769-
1770-
# Find threads and their statuses
1771-
statuses = {}
1772-
for interpreter_info in traces:
1773-
for thread_info in interpreter_info.threads:
1774-
statuses[thread_info.thread_id] = thread_info.status
1775-
17761778
self.assertIsNotNone(sleeper_tid, "Sleeper thread id not received")
17771779
self.assertIsNotNone(busy_tid, "Busy thread id not received")
17781780
self.assertIn(sleeper_tid, statuses, "Sleeper tid not found in sampled threads")
@@ -1861,28 +1863,30 @@ def busy():
18611863
break
18621864

18631865
attempts = 10
1866+
statuses = {}
18641867
try:
18651868
unwinder = RemoteUnwinder(p.pid, all_threads=True, mode=PROFILING_MODE_GIL,
18661869
skip_non_matching_threads=False)
18671870
for _ in range(attempts):
18681871
traces = unwinder.get_stack_trace()
1869-
# Check if any thread is running
1870-
if any(thread_info.status == 0 for interpreter_info in traces
1871-
for thread_info in interpreter_info.threads):
1872+
# Find threads and their statuses
1873+
statuses = {}
1874+
for interpreter_info in traces:
1875+
for thread_info in interpreter_info.threads:
1876+
statuses[thread_info.thread_id] = thread_info.status
1877+
1878+
# Check if sleeper thread is idle (status 2 for GIL mode) and busy thread is running
1879+
if (sleeper_tid in statuses and
1880+
busy_tid in statuses and
1881+
statuses[sleeper_tid] == 2 and
1882+
statuses[busy_tid] == 0):
18721883
break
18731884
time.sleep(0.5) # Give a bit of time to let threads settle
18741885
except PermissionError:
18751886
self.skipTest(
18761887
"Insufficient permissions to read the stack trace"
18771888
)
18781889

1879-
1880-
# Find threads and their statuses
1881-
statuses = {}
1882-
for interpreter_info in traces:
1883-
for thread_info in interpreter_info.threads:
1884-
statuses[thread_info.thread_id] = thread_info.status
1885-
18861890
self.assertIsNotNone(sleeper_tid, "Sleeper thread id not received")
18871891
self.assertIsNotNone(busy_tid, "Busy thread id not received")
18881892
self.assertIn(sleeper_tid, statuses, "Sleeper tid not found in sampled threads")

0 commit comments

Comments
 (0)