]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-148932: Fix `profiling.sampling` on Windows virtual environments (#150541)
authorEduardo Villalpando Mello <eduardovil@microsoft.com>
Mon, 8 Jun 2026 17:25:43 +0000 (10:25 -0700)
committerGitHub <noreply@github.com>
Mon, 8 Jun 2026 17:25:43 +0000 (17:25 +0000)
Doc/library/profiling.sampling.rst
Lib/profiling/sampling/sample.py
Misc/NEWS.d/next/Library/2026-05-27-23-47-31.gh-issue-148932.Y1xmvA.rst [new file with mode: 0644]

index 39b6ea4e31cde7293de676af4f819ba35f6a5ed3..aeb1a429b58515ac4deb5f153f309aae4b33c0da 100644 (file)
@@ -387,11 +387,6 @@ This requires one of:
 On Windows, the profiler requires administrative privileges or the
 ``SeDebugPrivilege`` privilege to read another process's memory.
 
-*Note*: On Windows, ``python -m profiling.sampling`` fails inside a virtual
-environment because the venv's ``python.exe`` is just a launcher shim that
-re-executes the base interpreter as a child process. The shim itself isn't
-a Python process and has no ``PyRuntime`` section to attach to. Instead,
-run it from the global Python installation.
 
 Version compatibility
 ---------------------
index 2d379e1e16a35e349c06963d50685c622bddd9a4..50ccc57566d70d37e240d5b8d43cd5552878b0f0 100644 (file)
@@ -50,9 +50,38 @@ MIN_SAMPLES_FOR_TUI = 200
 # Maximum number of consecutive identical samples to keep before flushing.
 MAX_PENDING_SAMPLES = 8192
 
+
+def _resolve_python_pid(pid):
+    """On Windows, if pid is a venvlauncher process, return the child Python PID.
+
+    The venvlauncher (used as python.exe in venvs) spawns the real Python
+    interpreter as a child process via CreateProcessW. The RemoteUnwinder
+    needs the child's PID, not the launcher's.
+
+    Returns the original pid if not on Windows, not a venv launcher,
+    or no child process is found.
+    """
+    if os.name != "nt" or sys.prefix == sys.base_prefix:
+        return pid
+    try:
+        children = _remote_debugging.get_child_pids(pid, recursive=False)
+        python_children = [
+            child for child in children
+            if _remote_debugging.is_python_process(child)
+        ]
+        if len(python_children) == 1:
+            return python_children[0]
+    except (OSError, RuntimeError) as err:
+        raise SystemExit(
+            f"Failed to initialize profiler from virtualenv: {err}\n"
+            f"Try running with the base interpreter: {sys._base_executable}"
+        ) from err
+    return pid
+
+
 class SampleProfiler:
     def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL, native=False, gc=True, opcodes=False, skip_non_matching_threads=True, collect_stats=False, blocking=False):
-        self.pid = pid
+        self.pid = _resolve_python_pid(pid)
         self.sample_interval_usec = sample_interval_usec
         self.all_threads = all_threads
         self.mode = mode  # Store mode for later use
@@ -61,10 +90,6 @@ class SampleProfiler:
         try:
             self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads)
         except RuntimeError as err:
-            if os.name == "nt" and sys.executable.endswith("python.exe"):
-                raise SystemExit(
-                    "Running profiling.sampling from virtualenv on Windows platform is not supported"
-                ) from err
             raise SystemExit(err) from err
         # Track sample intervals and total sample count
         self.sample_intervals = deque(maxlen=100)
diff --git a/Misc/NEWS.d/next/Library/2026-05-27-23-47-31.gh-issue-148932.Y1xmvA.rst b/Misc/NEWS.d/next/Library/2026-05-27-23-47-31.gh-issue-148932.Y1xmvA.rst
new file mode 100644 (file)
index 0000000..a0b7a97
--- /dev/null
@@ -0,0 +1 @@
+Fix ``profiling.sampling`` on Windows virtual environments to resolve the actual Python PID from a virtual environment shim.