]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-152356: Fix Windows blocking sampling after target process exit (GH-152471...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sun, 28 Jun 2026 17:27:42 +0000 (19:27 +0200)
committerGitHub <noreply@github.com>
Sun, 28 Jun 2026 17:27:42 +0000 (17:27 +0000)
Lib/test/test_profiling/test_sampling_profiler/test_blocking.py
Misc/NEWS.d/next/Library/2026-06-28-12-45-09.gh-issue-152356.Dr4w2Q.rst [new file with mode: 0644]
Modules/_remote_debugging/threads.c

index 1f4b6da3281056177e729c16af80be79edc675ed..0a5541c733d4c77a12d1d9f0e6654df4d15b4a35 100644 (file)
@@ -1,6 +1,9 @@
 """Tests for blocking mode sampling profiler."""
 
 import io
+import os
+import subprocess
+import sys
 import textwrap
 import unittest
 from unittest import mock
@@ -15,7 +18,11 @@ except ImportError:
         "Test only runs when _remote_debugging is available"
     )
 
-from test.support import requires_remote_subprocess_debugging
+from test.support import (
+    SHORT_TIMEOUT,
+    os_helper,
+    requires_remote_subprocess_debugging,
+)
 
 from .helpers import test_subprocess
 
@@ -158,3 +165,51 @@ class TestBlockingModeStackAccuracy(unittest.TestCase):
             f"fibonacci_generator appears in the stack when consume_generator "
             f"is the leaf frame on an arithmetic line. This indicates "
             f"torn/inconsistent stack traces are being captured.")
+
+
+@requires_remote_subprocess_debugging()
+@unittest.skipUnless(sys.platform == "win32", "Windows only")
+class TestBlockingModeCLI(unittest.TestCase):
+    def test_run_blocking_exits_after_target_process_exits(self):
+        script = 'print("done")\n'
+
+        tmpdir = os.path.abspath(os_helper.TESTFN + "_profiling_blocking")
+        with os_helper.temp_dir(tmpdir) as tmpdir:
+            script_path = os.path.join(tmpdir, "tiny_target.py")
+            profile_path = os.path.join(tmpdir, "blocking.bin")
+            with open(script_path, "w", encoding="utf-8") as file:
+                file.write(script)
+
+            cmd = [
+                sys.executable, "-m", "profiling.sampling", "run",
+                "--binary", "-o", profile_path,
+                "--mode=cpu", "--blocking", "-r", "100",
+                script_path,
+            ]
+            result = subprocess.run(
+                cmd,
+                cwd=tmpdir,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                text=True,
+                timeout=SHORT_TIMEOUT,
+            )
+
+            self.assertEqual(
+                result.returncode, 0,
+                f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}",
+            )
+            self.assertGreater(os.path.getsize(profile_path), 0)
+
+            replay = subprocess.run(
+                [sys.executable, "-m", "profiling.sampling", "replay",
+                 profile_path],
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+                text=True,
+                timeout=SHORT_TIMEOUT,
+            )
+            self.assertEqual(
+                replay.returncode, 0,
+                f"stdout:\n{replay.stdout}\nstderr:\n{replay.stderr}",
+            )
diff --git a/Misc/NEWS.d/next/Library/2026-06-28-12-45-09.gh-issue-152356.Dr4w2Q.rst b/Misc/NEWS.d/next/Library/2026-06-28-12-45-09.gh-issue-152356.Dr4w2Q.rst
new file mode 100644 (file)
index 0000000..6a4ceec
--- /dev/null
@@ -0,0 +1,3 @@
+Fix a hang in ``profiling.sampling run --blocking`` on Windows when the
+target process exits. The profiler now finalizes binary profiles instead of
+continuing to sample the exited process.
index 29f22f14c9b29f88757dad8c7adf93d5ad6f0562..04c70cc96d6bd1e4e9c12a3c71a89af728c6ef5b 100644 (file)
@@ -862,6 +862,12 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject *unwinder, _Py_RemoteDebug_T
         return 0;
     }
 
+    if (!is_process_alive(unwinder->handle.hProcess)) {
+        PyErr_Format(PyExc_ProcessLookupError,
+            "Process %d has terminated", unwinder->handle.pid);
+        return -1;
+    }
+
     PyErr_Format(PyExc_RuntimeError, "NtSuspendProcess failed: 0x%lx", status);
     return -1;
 }