[3.15] gh-152356: Fix Windows blocking sampling after target process exit (GH-152471) (#152512)

This commit is contained in:
Miss Islington (bot) 2026-06-28 19:27:42 +02:00 committed by GitHub
parent 09013312bb
commit f08fc4ad51
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 1 deletions

View 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 @@
"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 @@ def test_generator_not_under_consumer_arithmetic(self):
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}",
)

View file

@ -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.

View 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;
}