https://github.com/python/cpython/commit/77e7e6b81aee3864a887f5566d877d35b4356e4c
commit: 77e7e6b81aee3864a887f5566d877d35b4356e4c
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-22T11:01:58Z
summary:

[3.15] gh-159534: Fix race condition in test_sampling_profiler (GH-160028) 
(#160080)

gh-159534: Fix race condition in test_sampling_profiler (GH-160028)

Fix test_is_python_process_non_python_subprocess(). Wait until the
forked Python process calls exec() before calling is_python_process().
Previously, is_python_build() returned True if exec() was not called
yet in the forked process.
(cherry picked from commit 6fd15f0ea315af9883207518f692b1d3f3503350)

Co-authored-by: Victor Stinner <[email protected]>

files:
M Lib/test/test_profiling/test_sampling_profiler/test_children.py

diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_children.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_children.py
index e64d917eedde56b..4b27c6dbb1896c1 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_children.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_children.py
@@ -16,6 +16,7 @@
     SHORT_TIMEOUT,
     reap_children,
     requires_remote_subprocess_debugging,
+    sleeping_retry,
 )
 
 # Guard imports that require _remote_debugging module.
@@ -84,29 +85,13 @@ def reader():
     return result[0]
 
 
-def _wait_for_process_ready(proc, timeout):
-    # Wait for a subprocess to be ready using polling instead of fixed sleep.
-    # Returns True if process is ready, False if it exited or timeout.
-    deadline = time.time() + timeout
-    poll_interval = 0.01
-
-    while time.time() < deadline:
-        if proc.poll() is not None:
-            return False
-
-        try:
-            if sys.platform == "linux":
-                if os.path.exists(f"/proc/{proc.pid}/exe"):
-                    return True
-            else:
-                return True
-        except OSError:
-            pass
-
-        time.sleep(poll_interval)
-        poll_interval = min(poll_interval * 2, 0.1)
-
-    return proc.poll() is None
+def _wait_for_process_exec(proc, name, timeout):
+    # Wait until a child process calls exec() to replace the forked process
+    # executable. Raise AssertionError on timeout.
+    for _ in sleeping_retry(timeout, max_delay=0.1):
+        exe = os.readlink(f"/proc/{proc.pid}/exe")
+        if os.path.basename(exe) == name:
+            break
 
 
 @unittest.skipIf(
@@ -634,17 +619,13 @@ def test_is_python_process_non_python_subprocess(self):
         """Test that a non-Python subprocess is not detected as Python."""
         # Start a non-Python subprocess (sleep command)
         proc = subprocess.Popen(
-            ["sleep", "10"],
+            ["sleep", "60"],
             stdout=subprocess.DEVNULL,
             stderr=subprocess.DEVNULL,
         )
 
         try:
-            # Wait for process to be ready using polling
-            self.assertTrue(
-                _wait_for_process_ready(proc, SHORT_TIMEOUT),
-                f"Process {proc.pid} should be ready within {SHORT_TIMEOUT}s",
-            )
+            _wait_for_process_exec(proc, "sleep", SHORT_TIMEOUT)
 
             self.assertFalse(
                 is_python_process(proc.pid),

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to