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
40 changes: 40 additions & 0 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,12 @@ is resumed, and its locks reacquired. This means the critical section API
provides weaker guarantees than traditional locks -- they are useful because
their behavior is similar to the :term:`GIL`.

Variants that accept :c:type:`PyMutex` pointers rather than Python objects are also
available. Use these variants to start a critical section in a situation where
there is no :c:type:`PyObject` -- for example, when working with a C type that
does not extend or wrap :c:type:`PyObject` but still needs to call into the C
API in a manner that might lead to deadlocks.

The functions and structs used by the macros are exposed for cases
where C macros are not available. They should only be used as in the
given macro expansions. Note that the sizes and contents of the structures may
Expand Down Expand Up @@ -2351,6 +2357,23 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.

.. versionadded:: 3.13

.. c:macro:: Py_BEGIN_CRITICAL_SECTION_MUTEX(m)

Locks the mutex *m* and begins a critical section.

In the free-threaded build, this macro expands to::

{
PyCriticalSection _py_cs;
PyCriticalSection_BeginMutex(&_py_cs, m)

Note that unlike :c:macro:`Py_BEGIN_CRITICAL_SECTION`, there is no cast for
the argument of the macro - it must be a :c:type:`PyMutex` pointer.

On the default build, this macro expands to ``{``.

.. versionadded:: next

.. c:macro:: Py_END_CRITICAL_SECTION()

Ends the critical section and releases the per-object lock.
Expand Down Expand Up @@ -2380,6 +2403,23 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.

.. versionadded:: 3.13

.. c:macro:: Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2)

Locks the mutexes *m1* and *m2* and begins a critical section.

In the free-threaded build, this macro expands to::

{
PyCriticalSection2 _py_cs2;
PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)

Note that unlike :c:macro:`Py_BEGIN_CRITICAL_SECTION2`, there is no cast for
the arguments of the macro - they must be :c:type:`PyMutex` pointers.

On the default build, this macro expands to ``{``.

.. versionadded:: next

.. c:macro:: Py_END_CRITICAL_SECTION2()

Ends the critical section and releases the per-object locks.
Expand Down
18 changes: 9 additions & 9 deletions Doc/library/os.path.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: basename(path)
.. function:: basename(path, /)

Return the base name of pathname *path*. This is the second element of the
pair returned by passing *path* to the function :func:`split`. Note that
Expand Down Expand Up @@ -118,7 +118,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: dirname(path)
.. function:: dirname(path, /)

Return the directory name of pathname *path*. This is the first element of
the pair returned by passing *path* to the function :func:`split`.
Expand Down Expand Up @@ -237,7 +237,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: isabs(path)
.. function:: isabs(path, /)

Return ``True`` if *path* is an absolute pathname. On Unix, that means it
begins with a slash, on Windows that it begins with two (back)slashes, or a
Expand All @@ -261,7 +261,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: isdir(path)
.. function:: isdir(path, /)

Return ``True`` if *path* is an :func:`existing <exists>` directory. This
follows symbolic links, so both :func:`islink` and :func:`isdir` can be true
Expand Down Expand Up @@ -372,7 +372,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object` for *path* and *paths*.


.. function:: normcase(path)
.. function:: normcase(path, /)

Normalize the case of a pathname. On Windows, convert all characters in the
pathname to lowercase, and also convert forward slashes to backward slashes.
Expand Down Expand Up @@ -509,7 +509,7 @@ the :mod:`glob` module.)
Added Windows support.


.. function:: split(path)
.. function:: split(path, /)

Split the pathname *path* into a pair, ``(head, tail)`` where *tail* is the
last pathname component and *head* is everything leading up to that. The
Expand All @@ -525,7 +525,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: splitdrive(path)
.. function:: splitdrive(path, /)

Split the pathname *path* into a pair ``(drive, tail)`` where *drive* is either
a mount point or the empty string. On systems which do not use drive
Expand All @@ -550,7 +550,7 @@ the :mod:`glob` module.)
Accepts a :term:`path-like object`.


.. function:: splitroot(path)
.. function:: splitroot(path, /)

Split the pathname *path* into a 3-item tuple ``(drive, root, tail)`` where
*drive* is a device name or mount point, *root* is a string of separators
Expand Down Expand Up @@ -583,7 +583,7 @@ the :mod:`glob` module.)
.. versionadded:: 3.12


.. function:: splitext(path)
.. function:: splitext(path, /)

Split the pathname *path* into a pair ``(root, ext)`` such that ``root + ext ==
path``, and the extension, *ext*, is empty or begins with a period and contains at
Expand Down
3 changes: 3 additions & 0 deletions Doc/library/urllib.request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ The :mod:`urllib.request` module defines the following functions:
Windows a UNC path is returned (as before), and on other platforms a
:exc:`~urllib.error.URLError` is raised.

.. versionchanged:: 3.14
The URL query and fragment components are discarded if present.

.. versionchanged:: 3.14
The *require_scheme* and *resolve_host* parameters were added.

Expand Down
4 changes: 2 additions & 2 deletions Doc/tutorial/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -579,8 +579,8 @@ module for example, you might use::
from .. import formats
from ..filters import equalizer

Note that relative imports are based on the name of the current module. Since
the name of the main module is always ``"__main__"``, modules intended for use
Note that relative imports are based on the name of the current module's package.
Since the main module does not have a package, modules intended for use
as the main module of a Python application must always use absolute imports.


Expand Down
1 change: 1 addition & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,7 @@ urllib
- Discard URL authority if it matches the local hostname.
- Discard URL authority if it resolves to a local IP address when the new
*resolve_host* argument is set to true.
- Discard URL query and fragment components.
- Raise :exc:`~urllib.error.URLError` if a URL authority isn't local,
except on Windows where we return a UNC path as before.

Expand Down
20 changes: 20 additions & 0 deletions Include/cpython/critical_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,32 @@ typedef struct PyCriticalSection2 PyCriticalSection2;
PyAPI_FUNC(void)
PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op);

PyAPI_FUNC(void)
PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m);

PyAPI_FUNC(void)
PyCriticalSection_End(PyCriticalSection *c);

PyAPI_FUNC(void)
PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b);

PyAPI_FUNC(void)
PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2);

PyAPI_FUNC(void)
PyCriticalSection2_End(PyCriticalSection2 *c);

#ifndef Py_GIL_DISABLED
# define Py_BEGIN_CRITICAL_SECTION(op) \
{
# define Py_BEGIN_CRITICAL_SECTION_MUTEX(mutex) \
{
# define Py_END_CRITICAL_SECTION() \
}
# define Py_BEGIN_CRITICAL_SECTION2(a, b) \
{
# define Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2) \
{
# define Py_END_CRITICAL_SECTION2() \
}
#else /* !Py_GIL_DISABLED */
Expand Down Expand Up @@ -118,6 +128,11 @@ struct PyCriticalSection2 {
PyCriticalSection _py_cs; \
PyCriticalSection_Begin(&_py_cs, _PyObject_CAST(op))

# define Py_BEGIN_CRITICAL_SECTION_MUTEX(mutex) \
{ \
PyCriticalSection _py_cs; \
PyCriticalSection_BeginMutex(&_py_cs, mutex)

# define Py_END_CRITICAL_SECTION() \
PyCriticalSection_End(&_py_cs); \
}
Expand All @@ -127,6 +142,11 @@ struct PyCriticalSection2 {
PyCriticalSection2 _py_cs2; \
PyCriticalSection2_Begin(&_py_cs2, _PyObject_CAST(a), _PyObject_CAST(b))

# define Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2) \
{ \
PyCriticalSection2 _py_cs2; \
PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)

# define Py_END_CRITICAL_SECTION2() \
PyCriticalSection2_End(&_py_cs2); \
}
Expand Down
14 changes: 2 additions & 12 deletions Include/internal/pycore_critical_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ extern "C" {
#define _Py_CRITICAL_SECTION_MASK 0x3

#ifdef Py_GIL_DISABLED
# define Py_BEGIN_CRITICAL_SECTION_MUT(mutex) \
{ \
PyCriticalSection _py_cs; \
_PyCriticalSection_BeginMutex(&_py_cs, mutex)

# define Py_BEGIN_CRITICAL_SECTION2_MUT(m1, m2) \
{ \
PyCriticalSection2 _py_cs2; \
_PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)

// Specialized version of critical section locking to safely use
// PySequence_Fast APIs without the GIL. For performance, the argument *to*
// PySequence_Fast() is provided to the macro, not the *result* of
Expand Down Expand Up @@ -75,8 +65,6 @@ extern "C" {

#else /* !Py_GIL_DISABLED */
// The critical section APIs are no-ops with the GIL.
# define Py_BEGIN_CRITICAL_SECTION_MUT(mut) {
# define Py_BEGIN_CRITICAL_SECTION2_MUT(m1, m2) {
# define Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(original) {
# define Py_END_CRITICAL_SECTION_SEQUENCE_FAST() }
# define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex)
Expand Down Expand Up @@ -119,6 +107,7 @@ _PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m)
_PyCriticalSection_BeginSlow(c, m);
}
}
#define PyCriticalSection_BeginMutex _PyCriticalSection_BeginMutex

static inline void
_PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op)
Expand Down Expand Up @@ -194,6 +183,7 @@ _PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2)
_PyCriticalSection2_BeginSlow(c, m1, m2, 0);
}
}
#define PyCriticalSection2_BeginMutex _PyCriticalSection2_BeginMutex

static inline void
_PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extern PyStatus _Py_HashRandomization_Init(const PyConfig *);

extern PyStatus _PyGC_Init(PyInterpreterState *interp);
extern PyStatus _PyAtExit_Init(PyInterpreterState *interp);
extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp);

/* Various internal finalizers */

Expand Down
28 changes: 28 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -7295,6 +7295,34 @@ def test_update_type_cache(self):
""")
script_helper.assert_python_ok('-c', script)

def test_concurrent_initialization_subinterpreter(self):
# gh-136421: Concurrent initialization of _datetime across multiple
# interpreters wasn't thread-safe due to its static types.

# Run in a subprocess to ensure we get a clean version of _datetime
script = """if True:
from concurrent.futures import InterpreterPoolExecutor

def func():
import _datetime
print('a', end='')

with InterpreterPoolExecutor() as executor:
for _ in range(8):
executor.submit(func)
"""
rc, out, err = script_helper.assert_python_ok("-c", script)
self.assertEqual(rc, 0)
self.assertEqual(out, b"a" * 8)
self.assertEqual(err, b"")

# Now test against concurrent reinitialization
script = "import _datetime\n" + script
rc, out, err = script_helper.assert_python_ok("-c", script)
self.assertEqual(rc, 0)
self.assertEqual(out, b"a" * 8)
self.assertEqual(err, b"")


def load_tests(loader, standard_tests, pattern):
standard_tests.addTest(ZoneInfoCompleteTest())
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_free_threading/test_syslog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import unittest
import threading

from test.support import import_helper, threading_helper
from test.support.threading_helper import run_concurrently

syslog = import_helper.import_module("syslog")

NTHREADS = 32

# Similar to Lib/test/test_syslog.py, this test's purpose is to verify that
# the code neither crashes nor leaks.


@threading_helper.requires_working_threading()
class TestSyslog(unittest.TestCase):
def test_racing_syslog(self):
def worker():
"""
The syslog module provides the following functions:
openlog(), syslog(), closelog(), and setlogmask().
"""
thread_id = threading.get_ident()
syslog.openlog(f"thread-id: {thread_id}")
try:
for _ in range(5):
syslog.syslog("logline")
syslog.setlogmask(syslog.LOG_MASK(syslog.LOG_INFO))
syslog.syslog(syslog.LOG_INFO, "logline LOG_INFO")
syslog.setlogmask(syslog.LOG_MASK(syslog.LOG_ERR))
syslog.syslog(syslog.LOG_ERR, "logline LOG_ERR")
syslog.setlogmask(syslog.LOG_UPTO(syslog.LOG_DEBUG))
finally:
syslog.closelog()

# Run the worker concurrently to exercise all these syslog functions
run_concurrently(
worker_func=worker,
nthreads=NTHREADS,
)


if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions Lib/test/test_urllib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,14 @@ def test_url2pathname(self):
self.assertEqual(fn('////foo/bar'), f'{sep}{sep}foo{sep}bar')
self.assertEqual(fn('data:blah'), 'data:blah')
self.assertEqual(fn('data://blah'), f'data:{sep}{sep}blah')
self.assertEqual(fn('foo?bar'), 'foo')
self.assertEqual(fn('foo#bar'), 'foo')
self.assertEqual(fn('foo?bar=baz'), 'foo')
self.assertEqual(fn('foo?bar#baz'), 'foo')
self.assertEqual(fn('foo%3Fbar'), 'foo?bar')
self.assertEqual(fn('foo%23bar'), 'foo#bar')
self.assertEqual(fn('foo%3Fbar%3Dbaz'), 'foo?bar=baz')
self.assertEqual(fn('foo%3Fbar%23baz'), 'foo?bar#baz')

def test_url2pathname_require_scheme(self):
sep = os.path.sep
Expand Down
10 changes: 5 additions & 5 deletions Lib/urllib/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -1654,11 +1654,11 @@ def url2pathname(url, *, require_scheme=False, resolve_host=False):
The URL authority may be resolved with gethostbyname() if
*resolve_host* is set to true.
"""
if require_scheme:
scheme, url = _splittype(url)
if scheme != 'file':
raise URLError("URL is missing a 'file:' scheme")
authority, url = _splithost(url)
if not require_scheme:
url = 'file:' + url
scheme, authority, url = urlsplit(url)[:3] # Discard query and fragment.
if scheme != 'file':
raise URLError("URL is missing a 'file:' scheme")
if os.name == 'nt':
if not _is_local_authority(authority, resolve_host):
# e.g. file://server/share/file.txt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
New variants for the critical section API that accept one or two
:c:type:`PyMutex` pointers rather than :c:type:`PyObject` instances are now
public in the non-limited C API.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix crash when initializing :mod:`datetime` concurrently.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make functions in :mod:`syslog` thread-safe on the :term:`free threaded
<free threading>` build.
Loading
Loading