Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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}",
)
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions Modules/_remote_debugging/threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading