Skip to content

Commit b8f2a2c

Browse files
edvilmemiss-islington
authored andcommitted
gh-148932: Fix profiling.sampling on Windows virtual environments (GH-150541)
(cherry picked from commit 5c13217) Co-authored-by: Eduardo Villalpando Mello <eduardovil@microsoft.com>
1 parent 86e291e commit b8f2a2c

3 files changed

Lines changed: 31 additions & 10 deletions

File tree

Doc/library/profiling.sampling.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,6 @@ This requires one of:
387387
On Windows, the profiler requires administrative privileges or the
388388
``SeDebugPrivilege`` privilege to read another process's memory.
389389

390-
*Note*: On Windows, ``python -m profiling.sampling`` fails inside a virtual
391-
environment because the venv's ``python.exe`` is just a launcher shim that
392-
re-executes the base interpreter as a child process. The shim itself isn't
393-
a Python process and has no ``PyRuntime`` section to attach to. Instead,
394-
run it from the global Python installation.
395390

396391
Version compatibility
397392
---------------------

Lib/profiling/sampling/sample.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,38 @@ def _pause_threads(unwinder, blocking):
5050
# Maximum number of consecutive identical samples to keep before flushing.
5151
MAX_PENDING_SAMPLES = 8192
5252

53+
54+
def _resolve_python_pid(pid):
55+
"""On Windows, if pid is a venvlauncher process, return the child Python PID.
56+
57+
The venvlauncher (used as python.exe in venvs) spawns the real Python
58+
interpreter as a child process via CreateProcessW. The RemoteUnwinder
59+
needs the child's PID, not the launcher's.
60+
61+
Returns the original pid if not on Windows, not a venv launcher,
62+
or no child process is found.
63+
"""
64+
if os.name != "nt" or sys.prefix == sys.base_prefix:
65+
return pid
66+
try:
67+
children = _remote_debugging.get_child_pids(pid, recursive=False)
68+
python_children = [
69+
child for child in children
70+
if _remote_debugging.is_python_process(child)
71+
]
72+
if len(python_children) == 1:
73+
return python_children[0]
74+
except (OSError, RuntimeError) as err:
75+
raise SystemExit(
76+
f"Failed to initialize profiler from virtualenv: {err}\n"
77+
f"Try running with the base interpreter: {sys._base_executable}"
78+
) from err
79+
return pid
80+
81+
5382
class SampleProfiler:
5483
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):
55-
self.pid = pid
84+
self.pid = _resolve_python_pid(pid)
5685
self.sample_interval_usec = sample_interval_usec
5786
self.all_threads = all_threads
5887
self.mode = mode # Store mode for later use
@@ -61,10 +90,6 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD
6190
try:
6291
self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads)
6392
except RuntimeError as err:
64-
if os.name == "nt" and sys.executable.endswith("python.exe"):
65-
raise SystemExit(
66-
"Running profiling.sampling from virtualenv on Windows platform is not supported"
67-
) from err
6893
raise SystemExit(err) from err
6994
# Track sample intervals and total sample count
7095
self.sample_intervals = deque(maxlen=100)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``profiling.sampling`` on Windows virtual environments to resolve the actual Python PID from a virtual environment shim.

0 commit comments

Comments
 (0)