Skip to content

Commit

Permalink
Merge branch 'main' into pythongh-119357
Browse files Browse the repository at this point in the history
  • Loading branch information
eugenetriguba authored May 21, 2024
2 parents 6986565 + 9fa206a commit 10a06f0
Show file tree
Hide file tree
Showing 21 changed files with 527 additions and 407 deletions.
4 changes: 2 additions & 2 deletions Doc/c-api/module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -427,14 +427,14 @@ The available slot types are:
This slot is ignored by Python builds not configured with
:option:`--disable-gil`. Otherwise, it determines whether or not importing
this module will cause the GIL to be automatically enabled. See
:envvar:`PYTHON_GIL` and :option:`-X gil <-X>` for more detail.
:ref:`free-threaded-cpython` for more detail.
Multiple ``Py_mod_gil`` slots may not be specified in one module definition.
If ``Py_mod_gil`` is not specified, the import machinery defaults to
``Py_MOD_GIL_USED``.
.. versionadded: 3.13
.. versionadded:: 3.13
See :PEP:`489` for more details on multi-phase initialization.
Expand Down
6 changes: 3 additions & 3 deletions Doc/c-api/monitoring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ See :mod:`sys.monitoring` for descriptions of the events.
:c:func:`PyErr_GetRaisedException`).
.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
Fire a ``STOP_ITERATION`` event with the current exception (as returned by
:c:func:`PyErr_GetRaisedException`).
Fire a ``STOP_ITERATION`` event. If ``value`` is an instance of :exc:`StopIteration`, it is used. Otherwise,
a new :exc:`StopIteration` instance is created with ``value`` as its argument.
Managing the Monitoring State
Expand Down
5 changes: 3 additions & 2 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,8 @@ Miscellaneous options

* :samp:`-X gil={0,1}` forces the GIL to be disabled or enabled,
respectively. Only available in builds configured with
:option:`--disable-gil`. See also :envvar:`PYTHON_GIL`.
:option:`--disable-gil`. See also :envvar:`PYTHON_GIL` and
:ref:`free-threaded-cpython`.

.. versionadded:: 3.13

Expand Down Expand Up @@ -1212,7 +1213,7 @@ conflict.
forced on. Setting it to ``0`` forces the GIL off.

See also the :option:`-X gil <-X>` command-line option, which takes
precedence over this variable.
precedence over this variable, and :ref:`free-threaded-cpython`.

Needs Python configured with the :option:`--disable-gil` build option.

Expand Down
2 changes: 1 addition & 1 deletion Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ General Options
Defines the ``Py_GIL_DISABLED`` macro and adds ``"t"`` to
:data:`sys.abiflags`.

See :pep:`703` "Making the Global Interpreter Lock Optional in CPython".
See :ref:`free-threaded-cpython` for more detail.

.. versionadded:: 3.13

Expand Down
3 changes: 2 additions & 1 deletion Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ CPython will run with the :term:`global interpreter lock` (GIL) disabled when
configured using the ``--disable-gil`` option at build time. This is an
experimental feature and therefore isn't used by default. Users need to
either compile their own interpreter, or install one of the experimental
builds that are marked as *free-threaded*.
builds that are marked as *free-threaded*. See :pep:`703` "Making the Global
Interpreter Lock Optional in CPython" for more detail.

Free-threaded execution allows for full utilization of the available
processing power by running threads in parallel on available CPU cores.
Expand Down
6 changes: 3 additions & 3 deletions Include/cpython/monitoring.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ PyAPI_FUNC(int)
_PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset);

PyAPI_FUNC(int)
_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset);
_PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value);


#define _PYMONITORING_IF_ACTIVE(STATE, X) \
Expand Down Expand Up @@ -240,11 +240,11 @@ PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int
}

static inline int
PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *value)
{
_PYMONITORING_IF_ACTIVE(
state,
_PyMonitoring_FireStopIterationEvent(state, codelike, offset));
_PyMonitoring_FireStopIterationEvent(state, codelike, offset, value));
}

#undef _PYMONITORING_IF_ACTIVE
4 changes: 2 additions & 2 deletions Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 4 additions & 17 deletions Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,21 +288,6 @@ def dirname(p):
return split(p)[0]


# Is a path a junction?

if hasattr(os.stat_result, 'st_reparse_tag'):
def isjunction(path):
"""Test whether a path is a junction"""
try:
st = os.lstat(path)
except (OSError, ValueError, AttributeError):
return False
return st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT
else:
# Use genericpath.isjunction as imported above
pass


# Is a path a mount point?
# Any drive letter root (eg c:\)
# Any share UNC (eg \\server\share)
Expand Down Expand Up @@ -911,13 +896,15 @@ def commonpath(paths):


try:
# The isdir(), isfile(), islink() and exists() implementations in
# genericpath use os.stat(). This is overkill on Windows. Use simpler
# The isdir(), isfile(), islink(), exists() and lexists() implementations
# in genericpath use os.stat(). This is overkill on Windows. Use simpler
# builtin functions if they are available.
from nt import _path_isdir as isdir
from nt import _path_isfile as isfile
from nt import _path_islink as islink
from nt import _path_isjunction as isjunction
from nt import _path_exists as exists
from nt import _path_lexists as lexists
except ImportError:
# Use genericpath.* as imported above
pass
Expand Down
7 changes: 6 additions & 1 deletion Lib/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,12 @@ def register_readline():
pass

def write_history():
from _pyrepl.__main__ import CAN_USE_PYREPL
try:
# _pyrepl.__main__ is executed as the __main__ module
from __main__ import CAN_USE_PYREPL
except ImportError:
CAN_USE_PYREPL = False

try:
if os.getenv("PYTHON_BASIC_REPL") or not CAN_USE_PYREPL:
readline.write_history_file(history)
Expand Down
16 changes: 9 additions & 7 deletions Lib/test/test_genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ def test_exists(self):
self.assertIs(self.pathmodule.exists(filename), False)
self.assertIs(self.pathmodule.exists(bfilename), False)

self.assertIs(self.pathmodule.lexists(filename), False)
self.assertIs(self.pathmodule.lexists(bfilename), False)

create_file(filename)

self.assertIs(self.pathmodule.exists(filename), True)
Expand All @@ -145,14 +148,13 @@ def test_exists(self):
self.assertIs(self.pathmodule.exists(filename + '\x00'), False)
self.assertIs(self.pathmodule.exists(bfilename + b'\x00'), False)

if self.pathmodule is not genericpath:
self.assertIs(self.pathmodule.lexists(filename), True)
self.assertIs(self.pathmodule.lexists(bfilename), True)
self.assertIs(self.pathmodule.lexists(filename), True)
self.assertIs(self.pathmodule.lexists(bfilename), True)

self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False)
self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)
self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False)
self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)

@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat")
Expand Down
31 changes: 26 additions & 5 deletions Lib/test/test_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -1938,19 +1938,28 @@ def setUp(self):
( 1, E.RAISE, capi.fire_event_raise, ValueError(2)),
( 1, E.EXCEPTION_HANDLED, capi.fire_event_exception_handled, ValueError(5)),
( 1, E.PY_UNWIND, capi.fire_event_py_unwind, ValueError(6)),
( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, ValueError(7)),
( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, 7),
( 1, E.STOP_ITERATION, capi.fire_event_stop_iteration, StopIteration(8)),
]

self.EXPECT_RAISED_EXCEPTION = [E.PY_THROW, E.RAISE, E.EXCEPTION_HANDLED, E.PY_UNWIND]

def check_event_count(self, event, func, args, expected):

def check_event_count(self, event, func, args, expected, callback_raises=None):
class Counter:
def __init__(self):
def __init__(self, callback_raises):
self.callback_raises = callback_raises
self.count = 0

def __call__(self, *args):
self.count += 1
if self.callback_raises:
exc = self.callback_raises
self.callback_raises = None
raise exc

try:
counter = Counter()
counter = Counter(callback_raises)
sys.monitoring.register_callback(TEST_TOOL, event, counter)
if event == E.C_RETURN or event == E.C_RAISE:
sys.monitoring.set_events(TEST_TOOL, E.CALL)
Expand Down Expand Up @@ -1987,8 +1996,9 @@ def test_fire_event(self):

def test_missing_exception(self):
for _, event, function, *args in self.cases:
if not (args and isinstance(args[-1], BaseException)):
if event not in self.EXPECT_RAISED_EXCEPTION:
continue
assert args and isinstance(args[-1], BaseException)
offset = 0
self.codelike = _testcapi.CodeLike(1)
with self.subTest(function.__name__):
Expand All @@ -1997,6 +2007,17 @@ def test_missing_exception(self):
expected = ValueError(f"Firing event {evt} with no exception set")
self.check_event_count(event, function, args_, expected)

def test_fire_event_failing_callback(self):
for expected, event, function, *args in self.cases:
offset = 0
self.codelike = _testcapi.CodeLike(1)
with self.subTest(function.__name__):
args_ = (self.codelike, offset) + tuple(args)
exc = OSError(42)
with self.assertRaises(type(exc)):
self.check_event_count(event, function, args_, expected,
callback_raises=exc)


CANNOT_DISABLE = { E.PY_THROW, E.RAISE, E.RERAISE,
E.EXCEPTION_HANDLED, E.PY_UNWIND }
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,27 @@ def test_isfile_driveletter(self):
raise unittest.SkipTest('SystemDrive is not defined or malformed')
self.assertFalse(os.path.isfile('\\\\.\\' + drive))

@unittest.skipUnless(hasattr(os, 'pipe'), "need os.pipe()")
def test_isfile_anonymous_pipe(self):
pr, pw = os.pipe()
try:
self.assertFalse(ntpath.isfile(pr))
finally:
os.close(pr)
os.close(pw)

@unittest.skipIf(sys.platform != 'win32', "windows only")
def test_isfile_named_pipe(self):
import _winapi
named_pipe = f'//./PIPE/python_isfile_test_{os.getpid()}'
h = _winapi.CreateNamedPipe(named_pipe,
_winapi.PIPE_ACCESS_INBOUND,
0, 1, 0, 0, 0, 0)
try:
self.assertFalse(ntpath.isfile(named_pipe))
finally:
_winapi.CloseHandle(h)

@unittest.skipIf(sys.platform != 'win32', "windows only")
def test_con_device(self):
self.assertFalse(os.path.isfile(r"\\.\CON"))
Expand All @@ -1114,8 +1135,12 @@ def test_fast_paths_in_use(self):
self.assertFalse(inspect.isfunction(os.path.isfile))
self.assertTrue(os.path.islink is nt._path_islink)
self.assertFalse(inspect.isfunction(os.path.islink))
self.assertTrue(os.path.isjunction is nt._path_isjunction)
self.assertFalse(inspect.isfunction(os.path.isjunction))
self.assertTrue(os.path.exists is nt._path_exists)
self.assertFalse(inspect.isfunction(os.path.exists))
self.assertTrue(os.path.lexists is nt._path_lexists)
self.assertFalse(inspect.isfunction(os.path.lexists))

@unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32")
def test_isdevdrive(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Speedup :func:`os.path.isjunction` and :func:`os.path.lexists` on Windows with a native implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Avoid creating unnecessary :exc:`StopIteration` instances for monitoring.
9 changes: 5 additions & 4 deletions Modules/_testcapi/monitoring.c
Original file line number Diff line number Diff line change
Expand Up @@ -416,16 +416,17 @@ fire_event_stop_iteration(PyObject *self, PyObject *args)
{
PyObject *codelike;
int offset;
PyObject *exception;
if (!PyArg_ParseTuple(args, "OiO", &codelike, &offset, &exception)) {
PyObject *value;
if (!PyArg_ParseTuple(args, "OiO", &codelike, &offset, &value)) {
return NULL;
}
NULLABLE(exception);
NULLABLE(value);
PyObject *exception = NULL;
PyMonitoringState *state = setup_fire(codelike, offset, exception);
if (state == NULL) {
return NULL;
}
int res = PyMonitoring_FireStopIterationEvent(state, codelike, offset);
int res = PyMonitoring_FireStopIterationEvent(state, codelike, offset, value);
RETURN_INT(teardown_fire(res, state, exception));
}

Expand Down
Loading

0 comments on commit 10a06f0

Please sign in to comment.