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
2 changes: 1 addition & 1 deletion Doc/library/collections.abc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ Notes on using :class:`Set` and :class:`MutableSet` as a mixin:
The :class:`Set` mixin provides a :meth:`!_hash` method to compute a hash value
for the set; however, :meth:`~object.__hash__` is not defined because not all sets
are :term:`hashable` or immutable. To add set hashability using mixins,
inherit from both :meth:`Set` and :meth:`Hashable`, then define
inherit from both :class:`Set` and :class:`Hashable`, then define
``__hash__ = Set._hash``.

.. seealso::
Expand Down
5 changes: 0 additions & 5 deletions Doc/library/profiling.sampling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------
Expand Down
35 changes: 30 additions & 5 deletions Lib/profiling/sampling/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,38 @@ def _pause_threads(unwinder, blocking):
# 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
Expand All @@ -61,10 +90,6 @@ def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MOD
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)
Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,15 @@ def test_import_raises_ModuleNotFoundError(self):
with self.assertRaises(ModuleNotFoundError):
import something_that_should_not_exist_anywhere

def test_import_null_byte_in_name_raises_ModuleNotFoundError(self):
# gh-150633: module names containing null bytes should not
# lead to duplicates in sys.modules
before = set(sys.modules)
with self.assertRaises(ModuleNotFoundError):
__import__('zipimport\x00junk')

self.assertEqual(set(sys.modules), before)

def test_from_import_missing_module_raises_ModuleNotFoundError(self):
with self.assertRaises(ModuleNotFoundError):
from something_that_should_not_exist_anywhere import blah
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix the frozen importer accepting module names with embedded null bytes, which
caused it to bypass the :data:`sys.modules` cache and create duplicate module
objects.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``profiling.sampling`` on Windows virtual environments to resolve the actual Python PID from a virtual environment shim.
2 changes: 1 addition & 1 deletion Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -3174,7 +3174,7 @@ find_frozen(PyObject *nameobj, struct frozen_info *info)
if (nameobj == NULL || nameobj == Py_None) {
return FROZEN_BAD_NAME;
}
const char *name = PyUnicode_AsUTF8(nameobj);
const char *name = _PyUnicode_AsUTF8NoNUL(nameobj);
if (name == NULL) {
// Note that this function previously used
// _PyUnicode_EqualToASCIIString(). We clear the error here
Expand Down
Loading