Skip to content

Commit 06aaee0

Browse files
committed
Merge branch 'main' into perf/hashlib/mutex-135239
2 parents 4f9729e + d08b4b2 commit 06aaee0

26 files changed

+701
-184
lines changed

Doc/c-api/exceptions.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,16 @@ Exception Classes
749749
.. versionadded:: 3.2
750750
751751
752+
.. c:function:: int PyExceptionClass_Check(PyObject *ob)
753+
754+
Return non-zero if *ob* is an exception class, zero otherwise. This function always succeeds.
755+
756+
757+
.. c:function:: const char *PyExceptionClass_Name(PyObject *ob)
758+
759+
Return :c:member:`~PyTypeObject.tp_name` of the exception class *ob*.
760+
761+
752762
Exception Objects
753763
=================
754764

Doc/library/dialog.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ is the base class for dialogs defined in other supporting modules.
220220

221221
.. class:: Dialog(master=None, **options)
222222

223-
.. method:: show(color=None, **options)
223+
.. method:: show(**options)
224224

225225
Render the Dialog window.
226226

Doc/library/sys.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,15 @@ always available. Unless explicitly noted otherwise, all variables are read-only
11851185
``cache_tag`` is set to ``None``, it indicates that module caching should
11861186
be disabled.
11871187

1188+
*supports_isolated_interpreters* is a boolean value, whether
1189+
this implementation supports multiple isolated interpreters.
1190+
It is ``True`` for CPython on most platforms. Platforms with
1191+
this support implement the low-level :mod:`!_interpreters` module.
1192+
1193+
.. seealso::
1194+
1195+
:pep:`684`, :pep:`734`, and :mod:`concurrent.interpreters`.
1196+
11881197
:data:`sys.implementation` may contain additional attributes specific to
11891198
the Python implementation. These non-standard attributes must start with
11901199
an underscore, and are not described here. Regardless of its contents,
@@ -1194,6 +1203,9 @@ always available. Unless explicitly noted otherwise, all variables are read-only
11941203

11951204
.. versionadded:: 3.3
11961205

1206+
.. versionchanged:: 3.14
1207+
Added ``supports_isolated_interpreters`` field.
1208+
11971209
.. note::
11981210

11991211
The addition of new required attributes must go through the normal PEP

Doc/library/uuid.rst

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,43 +193,52 @@ The :mod:`uuid` module defines the following functions:
193193

194194
.. function:: uuid1(node=None, clock_seq=None)
195195

196-
Generate a UUID from a host ID, sequence number, and the current time. If *node*
197-
is not given, :func:`getnode` is used to obtain the hardware address. If
198-
*clock_seq* is given, it is used as the sequence number; otherwise a random
199-
14-bit sequence number is chosen.
196+
Generate a UUID from a host ID, sequence number, and the current time
197+
according to :rfc:`RFC 9562, §5.1 <9562#section-5.1>`.
198+
199+
When *node* is not specified, :func:`getnode` is used to obtain the hardware
200+
address as a 48-bit positive integer. When a sequence number *clock_seq* is
201+
not specified, a pseudo-random 14-bit positive integer is generated.
202+
203+
If *node* or *clock_seq* exceed their expected bit count,
204+
only their least significant bits are kept.
200205

201206

202207
.. function:: uuid3(namespace, name)
203208

204209
Generate a UUID based on the MD5 hash of a namespace identifier (which is a
205210
UUID) and a name (which is a :class:`bytes` object or a string
206-
that will be encoded using UTF-8).
211+
that will be encoded using UTF-8)
212+
according to :rfc:`RFC 9562, §5.3 <9562#section-5.3>`.
207213

208214

209215
.. function:: uuid4()
210216

211-
Generate a random UUID.
217+
Generate a random UUID in a cryptographically-secure method
218+
according to :rfc:`RFC 9562, §5.4 <9562#section-5.4>`.
212219

213220

214221
.. function:: uuid5(namespace, name)
215222

216223
Generate a UUID based on the SHA-1 hash of a namespace identifier (which is a
217224
UUID) and a name (which is a :class:`bytes` object or a string
218-
that will be encoded using UTF-8).
225+
that will be encoded using UTF-8)
226+
according to :rfc:`RFC 9562, §5.5 <9562#section-5.5>`.
219227

220228

221229
.. function:: uuid6(node=None, clock_seq=None)
222230

223231
Generate a UUID from a sequence number and the current time according to
224-
:rfc:`9562`.
232+
:rfc:`RFC 9562, §5.6 <9562#section-5.6>`.
233+
225234
This is an alternative to :func:`uuid1` to improve database locality.
226235

227236
When *node* is not specified, :func:`getnode` is used to obtain the hardware
228237
address as a 48-bit positive integer. When a sequence number *clock_seq* is
229238
not specified, a pseudo-random 14-bit positive integer is generated.
230239

231-
If *node* or *clock_seq* exceed their expected bit count, only their least
232-
significant bits are kept.
240+
If *node* or *clock_seq* exceed their expected bit count,
241+
only their least significant bits are kept.
233242

234243
.. versionadded:: 3.14
235244

Include/internal/pycore_critical_section.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ extern "C" {
6464

6565
# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \
6666
if (Py_REFCNT(op) != 1) { \
67-
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyObject_CAST(op)->ob_mutex); \
67+
_PyCriticalSection_AssertHeldObj(_PyObject_CAST(op)); \
6868
}
6969

7070
#else /* Py_DEBUG */
@@ -239,6 +239,28 @@ _PyCriticalSection_AssertHeld(PyMutex *mutex)
239239
#endif
240240
}
241241

242+
static inline void
243+
_PyCriticalSection_AssertHeldObj(PyObject *op)
244+
{
245+
#ifdef Py_DEBUG
246+
PyMutex *mutex = &_PyObject_CAST(op)->ob_mutex;
247+
PyThreadState *tstate = _PyThreadState_GET();
248+
uintptr_t prev = tstate->critical_section;
249+
if (prev & _Py_CRITICAL_SECTION_TWO_MUTEXES) {
250+
PyCriticalSection2 *cs = (PyCriticalSection2 *)(prev & ~_Py_CRITICAL_SECTION_MASK);
251+
_PyObject_ASSERT_WITH_MSG(op,
252+
(cs != NULL && (cs->_cs_base._cs_mutex == mutex || cs->_cs_mutex2 == mutex)),
253+
"Critical section of object is not held");
254+
}
255+
else {
256+
PyCriticalSection *cs = (PyCriticalSection *)(prev & ~_Py_CRITICAL_SECTION_MASK);
257+
_PyObject_ASSERT_WITH_MSG(op,
258+
(cs != NULL && cs->_cs_mutex == mutex),
259+
"Critical section of object is not held");
260+
}
261+
262+
#endif
263+
}
242264
#endif /* Py_GIL_DISABLED */
243265

244266
#ifdef __cplusplus

Lib/test/test_capi/test_misc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,11 +413,13 @@ def test_trashcan_subclass(self):
413413

414414
@support.requires_resource('cpu')
415415
@support.skip_emscripten_stack_overflow()
416+
@support.skip_wasi_stack_overflow()
416417
def test_trashcan_python_class1(self):
417418
self.do_test_trashcan_python_class(list)
418419

419420
@support.requires_resource('cpu')
420421
@support.skip_emscripten_stack_overflow()
422+
@support.skip_wasi_stack_overflow()
421423
def test_trashcan_python_class2(self):
422424
from _testcapi import MyList
423425
self.do_test_trashcan_python_class(MyList)

Lib/test/test_capi/test_opt.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,49 @@ def testfunc(n):
19681968
self.assertNotIn("_GUARD_NOS_INT", uops)
19691969
self.assertNotIn("_GUARD_TOS_INT", uops)
19701970

1971+
def test_call_len_known_length_small_int(self):
1972+
def testfunc(n):
1973+
x = 0
1974+
for _ in range(n):
1975+
t = (1, 2, 3, 4, 5)
1976+
if len(t) == 5:
1977+
x += 1
1978+
return x
1979+
1980+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
1981+
self.assertEqual(res, TIER2_THRESHOLD)
1982+
self.assertIsNotNone(ex)
1983+
uops = get_opnames(ex)
1984+
# When the length is < _PY_NSMALLPOSINTS, the len() call is replaced
1985+
# with just an inline load.
1986+
self.assertNotIn("_CALL_LEN", uops)
1987+
self.assertNotIn("_POP_CALL_ONE_LOAD_CONST_INLINE_BORROW", uops)
1988+
self.assertNotIn("_POP_CALL_LOAD_CONST_INLINE_BORROW", uops)
1989+
self.assertNotIn("_POP_TOP_LOAD_CONST_INLINE_BORROW", uops)
1990+
1991+
def test_call_len_known_length(self):
1992+
def testfunc(n):
1993+
class C:
1994+
t = tuple(range(300))
1995+
1996+
x = 0
1997+
for _ in range(n):
1998+
if len(C.t) == 300: # comparison + guard removed
1999+
x += 1
2000+
return x
2001+
2002+
res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
2003+
self.assertEqual(res, TIER2_THRESHOLD)
2004+
self.assertIsNotNone(ex)
2005+
uops = get_opnames(ex)
2006+
# When the length is >= _PY_NSMALLPOSINTS, we cannot replace
2007+
# the len() call with an inline load, but knowing the exact
2008+
# length allows us to optimize more code, such as conditionals
2009+
# in this case
2010+
self.assertIn("_CALL_LEN", uops)
2011+
self.assertNotIn("_COMPARE_OP_INT", uops)
2012+
self.assertNotIn("_GUARD_IS_TRUE_POP", uops)
2013+
19712014
def test_get_len_with_const_tuple(self):
19722015
def testfunc(n):
19732016
x = 0.0

Lib/test/test_free_threading/test_heapq.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import heapq
44

55
from enum import Enum
6-
from threading import Thread, Barrier
6+
from threading import Thread, Barrier, Lock
77
from random import shuffle, randint
88

99
from test.support import threading_helper
@@ -178,6 +178,33 @@ def heapreplace_max_func(max_heap, replace_items):
178178
self.assertEqual(len(max_heap), OBJECT_COUNT)
179179
self.test_heapq.check_max_invariant(max_heap)
180180

181+
def test_lock_free_list_read(self):
182+
n, n_threads = 1_000, 10
183+
l = []
184+
barrier = Barrier(n_threads * 2)
185+
186+
count = 0
187+
lock = Lock()
188+
189+
def worker():
190+
with lock:
191+
nonlocal count
192+
x = count
193+
count += 1
194+
195+
barrier.wait()
196+
for i in range(n):
197+
if x % 2:
198+
heapq.heappush(l, 1)
199+
heapq.heappop(l)
200+
else:
201+
try:
202+
l[0]
203+
except IndexError:
204+
pass
205+
206+
self.run_concurrently(worker, (), n_threads * 2)
207+
181208
@staticmethod
182209
def is_sorted_ascending(lst):
183210
"""

Lib/test/test_hashlib.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ def hash_constructors(self):
207207
constructors = self.constructors_to_test.values()
208208
return itertools.chain.from_iterable(constructors)
209209

210+
@property
211+
def shake_constructors(self):
212+
for shake_name in self.shakes:
213+
yield from self.constructors_to_test.get(shake_name, ())
214+
210215
@property
211216
def is_fips_mode(self):
212217
return get_fips_mode()
@@ -376,21 +381,50 @@ def test_hexdigest(self):
376381
self.assertIsInstance(h.digest(), bytes)
377382
self.assertEqual(hexstr(h.digest()), h.hexdigest())
378383

379-
def test_digest_length_overflow(self):
380-
# See issue #34922
381-
large_sizes = (2**29, 2**32-10, 2**32+10, 2**61, 2**64-10, 2**64+10)
382-
for cons in self.hash_constructors:
383-
h = cons(usedforsecurity=False)
384-
if h.name not in self.shakes:
385-
continue
386-
if HASH is not None and isinstance(h, HASH):
387-
# _hashopenssl's take a size_t
388-
continue
389-
for digest in h.digest, h.hexdigest:
390-
self.assertRaises(ValueError, digest, -10)
391-
for length in large_sizes:
392-
with self.assertRaises((ValueError, OverflowError)):
393-
digest(length)
384+
def test_shakes_zero_digest_length(self):
385+
for constructor in self.shake_constructors:
386+
with self.subTest(constructor=constructor):
387+
h = constructor(b'abcdef', usedforsecurity=False)
388+
self.assertEqual(h.digest(0), b'')
389+
self.assertEqual(h.hexdigest(0), '')
390+
391+
def test_shakes_invalid_digest_length(self):
392+
# See https://github.com/python/cpython/issues/79103.
393+
for constructor in self.shake_constructors:
394+
with self.subTest(constructor=constructor):
395+
h = constructor(usedforsecurity=False)
396+
# Note: digest() and hexdigest() take a signed input and
397+
# raise if it is negative; the rationale is that we use
398+
# internally PyBytes_FromStringAndSize() and _Py_strhex()
399+
# which both take a Py_ssize_t.
400+
for negative_size in (-1, -10, -(1 << 31), -sys.maxsize):
401+
self.assertRaises(ValueError, h.digest, negative_size)
402+
self.assertRaises(ValueError, h.hexdigest, negative_size)
403+
404+
def test_shakes_overflow_digest_length(self):
405+
# See https://github.com/python/cpython/issues/135759.
406+
407+
exc_types = (OverflowError, ValueError)
408+
# HACL* accepts an 'uint32_t' while OpenSSL accepts a 'size_t'.
409+
openssl_overflown_sizes = (sys.maxsize + 1, 2 * sys.maxsize)
410+
# https://github.com/python/cpython/issues/79103 restricts
411+
# the accepted built-in lengths to 2 ** 29, even if OpenSSL
412+
# accepts such lengths.
413+
builtin_overflown_sizes = openssl_overflown_sizes + (
414+
2 ** 29, 2 ** 32 - 10, 2 ** 32, 2 ** 32 + 10,
415+
2 ** 61, 2 ** 64 - 10, 2 ** 64, 2 ** 64 + 10,
416+
)
417+
418+
for constructor in self.shake_constructors:
419+
with self.subTest(constructor=constructor):
420+
h = constructor(usedforsecurity=False)
421+
if HASH is not None and isinstance(h, HASH):
422+
overflown_sizes = openssl_overflown_sizes
423+
else:
424+
overflown_sizes = builtin_overflown_sizes
425+
for invalid_size in overflown_sizes:
426+
self.assertRaises(exc_types, h.digest, invalid_size)
427+
self.assertRaises(exc_types, h.hexdigest, invalid_size)
394428

395429
def test_name_attribute(self):
396430
for cons in self.hash_constructors:

0 commit comments

Comments
 (0)