Skip to content

Commit 6a48f02

Browse files
committed
Merge branch 'main' into atan2
2 parents 6a92490 + 9d22a53 commit 6a48f02

26 files changed

Lines changed: 392 additions & 158 deletions

File tree

‎Doc/c-api/exceptions.rst‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,10 @@ because the :ref:`call protocol <call>` takes care of recursion handling.
10341034
case, a :exc:`RecursionError` is set and a nonzero value is returned.
10351035
Otherwise, zero is returned.
10361036
1037+
The check is based on the remaining C stack space of the current thread,
1038+
not on a count of calls, so it is unaffected by
1039+
:c:func:`Py_SetRecursionLimit` and :func:`sys.setrecursionlimit`.
1040+
10371041
*where* should be a UTF-8 encoded string such as ``" in instance check"`` to
10381042
be concatenated to the :exc:`RecursionError` message caused by the recursion
10391043
depth limit.
@@ -1044,6 +1048,10 @@ because the :ref:`call protocol <call>` takes care of recursion handling.
10441048
.. versionchanged:: 3.9
10451049
This function is now also available in the :ref:`limited API <limited-c-api>`.
10461050
1051+
.. versionchanged:: 3.14
1052+
The check is based on the remaining C stack space. Previously, a
1053+
separate counter of C-level calls was used.
1054+
10471055
.. c:function:: void Py_LeaveRecursiveCall(void)
10481056
10491057
Ends a :c:func:`Py_EnterRecursiveCall`. Must be called once for each

‎Doc/c-api/interp-lifecycle.rst‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ Process-wide parameters
514514
Return the version of this Python interpreter. This is a string that looks
515515
something like ::
516516
517-
"3.0a5+ (py3k:63103M, May 12 2008, 00:53:55) \n[GCC 4.2.3]"
517+
"3.15.0rc2 (3.15.0~rc2-1.fc44.x86_64, Sep 3 2026, 00:00:00) [GCC 16.2.1 20260819 (Red Hat 16.2.1-2)]"
518518
519519
.. index:: single: version (in module sys)
520520

‎Doc/whatsnew/3.15.rst‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,10 @@ name:
188188
lazy import json
189189
lazy from pathlib import Path
190190
191-
print("Starting up...") # json and pathlib not loaded yet
191+
print("Starting up...") # json and pathlib are not fully imported yet
192192
193-
data = json.loads('{"key": "value"}') # json loads here
194-
p = Path(".") # pathlib loads here
193+
data = json.loads('{"key": "value"}') # json gets fully imported here
194+
p = Path(".") # pathlib gets fully imported here
195195
196196
This mechanism is particularly useful for applications that import many
197197
modules at the top level but may only use a subset of them in any given run.

‎Include/internal/pycore_pylifecycle.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extern int _Py_IsLocaleCoercionTarget(const char *ctype_loc);
2323

2424
/* Various one-time initializers */
2525

26-
extern void _Py_InitVersion(void);
26+
extern PyStatus _Py_GetVersion_Init(void);
2727
extern PyStatus _PyFaulthandler_Init(int enable);
2828
extern PyObject * _PyBuiltin_Init(PyInterpreterState *interp);
2929
extern int _PyBuiltin_InitPythonFunctions(PyObject *dict);
@@ -43,6 +43,7 @@ extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp);
4343

4444
/* Various internal finalizers */
4545

46+
extern void _Py_GetVersion_Fini(void);
4647
extern int _PySignal_Init(int install_signal_handlers);
4748
extern void _PySignal_Fini(void);
4849

‎Lib/asyncio/tasks.py‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ def cancel(self, msg=None):
786786
return ret
787787

788788

789-
def _discard_awaited_by(children, waiter, outer):
789+
def _discard_awaited_by(children, waiter):
790790
for fut in children:
791791
futures.future_discard_from_awaited_by(fut, waiter)
792792

@@ -852,11 +852,14 @@ def _done_callback(fut, cur_task=cur_task):
852852
# 'fut.exception()' will *raise* a CancelledError
853853
# instead of returning it.
854854
exc = fut._make_cancelled_error()
855+
# gh-157213: children outliving gather() must lose the edge
856+
_discard_awaited_by(children, cur_task)
855857
outer.set_exception(exc)
856858
return
857859
else:
858860
exc = fut.exception()
859861
if exc is not None:
862+
_discard_awaited_by(children, cur_task)
860863
outer.set_exception(exc)
861864
return
862865

@@ -924,10 +927,6 @@ def _done_callback(fut, cur_task=cur_task):
924927
children.append(fut)
925928

926929
outer = _GatheringFuture(children, loop=loop)
927-
if cur_task is not None:
928-
# gh-157213: a child outliving gather() must lose the awaited-by edge
929-
outer.add_done_callback(
930-
functools.partial(_discard_awaited_by, children, cur_task))
931930
# Run done callbacks after GatheringFuture created so any post-processing
932931
# can be performed at this point
933932
# optimization: in the special case that *all* futures finished eagerly,

‎Lib/test/libregrtest/findtests.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"test_asyncio",
2222
"test_concurrent_futures",
2323
"test_doctests",
24+
"test_free_threading",
2425
"test_future_stmt",
2526
"test_gdb",
2627
"test_inspect",

‎Lib/test/test_asyncio/test_tasks.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,37 @@ async def coro():
12501250

12511251
self.loop.run_until_complete(self.new_task(self.loop, coro()))
12521252

1253+
def test_gather_discards_awaited_by_for_cancelled_sibling(self):
1254+
# gh-157213: same, when gather() is ended by a cancelled child
1255+
async def survivor():
1256+
await asyncio.Future()
1257+
1258+
async def coro():
1259+
t = self.new_task(self.loop, survivor())
1260+
victim = self.new_task(self.loop, asyncio.sleep(10))
1261+
victim.cancel()
1262+
with self.assertRaises(asyncio.CancelledError):
1263+
await asyncio.gather(t, victim)
1264+
self.assertFalse(t._asyncio_awaited_by)
1265+
t.cancel()
1266+
with self.assertRaises(asyncio.CancelledError):
1267+
await t
1268+
1269+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1270+
1271+
def test_gather_does_not_add_callback_to_outer(self):
1272+
# gh-158239: gather() must not add an internal done callback to
1273+
# the outer future just to maintain the await graph.
1274+
async def child():
1275+
await asyncio.sleep(0)
1276+
1277+
async def coro():
1278+
outer = asyncio.gather(child(), child())
1279+
self.assertFalse(outer._callbacks)
1280+
await outer
1281+
1282+
self.loop.run_until_complete(self.new_task(self.loop, coro()))
1283+
12531284
def test_wait_really_done(self):
12541285
# there is possibility that some tasks in the pending list
12551286
# became done but their callbacks haven't all been called yet

‎Lib/test/test_bytes.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,15 @@ def test_fromhex(self):
518518
self.type2test.fromhex(data)
519519
self.assertIn('at position %s' % pos, str(cm.exception))
520520

521+
# gh-158583: Check for out of bounds reads (uninitialized bytes).
522+
# Create an array from a list to not overallocate.
523+
a = array.array('B', list(b'1234 ')) # Py_ISSPACE() loop
524+
self.assertEqual(self.type2test.fromhex(a), b'\x12\x34')
525+
526+
a = array.array('B', list(b'12345')) # Missing second digit
527+
with self.assertRaises(ValueError):
528+
self.type2test.fromhex(a)
529+
521530
def test_hex(self):
522531
self.assertRaises(TypeError, self.type2test.hex)
523532
self.assertRaises(TypeError, self.type2test.hex, 1)

‎Lib/test/test_capi/test_build.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,11 @@ def test_getcopyright(self):
2020

2121
# Test Py_GetCompiler()
2222
def test_getcompiler(self):
23-
compiler = _testlimitedcapi.py_getcompiler()
24-
self.assertIsInstance(compiler, str)
25-
self.assertNotEqual(compiler, '')
23+
self.assertIn(_testlimitedcapi.py_getcompiler(), sys.version)
2624

2725
# Test Py_GetBuildInfo()
2826
def test_getbuildinfo(self):
29-
buildinfo = _testlimitedcapi.py_getbuildinfo()
30-
self.assertIsInstance(buildinfo, str)
31-
self.assertNotEqual(buildinfo, '')
27+
self.assertIn(_testlimitedcapi.py_getbuildinfo(), sys.version)
3228

3329
# Test internal _Py_GetBuiltWithAssert()
3430
def test__getbuiltwithassert(self):

‎Lib/test/test_capi/test_bytes.py‎

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -239,39 +239,60 @@ def test_resize(self):
239239
"""Test _PyBytes_Resize()"""
240240
_resize = _testcapi.bytes_resize
241241

242-
def resize(obj, size, new):
243-
result = _resize(obj, size, new)
244-
if 1 <= len(result):
245-
if new or size != len(obj):
246-
# gh-156995: Make sure that the result is a fresh object.
247-
# Previously, _PyBytes_Resize(&obj, 1) returned a singleton
248-
# if _PyObject_IsUniquelyReferenced() is false.
249-
self.assertEqual(sys.getrefcount(result), 1)
250-
self.assertFalse(sys._is_immortal(result))
251-
else:
242+
def assert_is_fresh_copy(result, refcnt, is_new_obj):
243+
self.assertEqual(refcnt, 1)
244+
self.assertTrue(is_new_obj)
245+
self.assertFalse(sys._is_immortal(result))
246+
247+
def resize(obj, size, new, compute_hash=False):
248+
old_size = len(obj)
249+
result, refcnt, is_new_obj = _resize(obj, size, new,
250+
compute_hash=compute_hash)
251+
252+
if size == old_size:
253+
# Return the same object unchanged
254+
self.assertFalse(is_new_obj)
255+
elif old_size == 0:
256+
assert_is_fresh_copy(result, refcnt, is_new_obj)
257+
elif size == 0:
252258
# check that the result is the empty bytes string singleton
259+
self.assertEqual(result, b'')
253260
self.assertTrue(sys._is_immortal(result))
261+
self.assertTrue(is_new_obj)
262+
elif (not new) or compute_hash:
263+
# gh-156995: Make sure that the result is a fresh object.
264+
# Previously, _PyBytes_Resize(&obj, 1) returned a singleton
265+
# if _PyObject_IsUniquelyReferenced() is false.
266+
assert_is_fresh_copy(result, refcnt, is_new_obj)
267+
else:
268+
# An in-place resize can return the same memory address, or
269+
# not. 'is_new_obj' cannot be tested.
270+
self.assertEqual(refcnt, 1)
271+
self.assertFalse(sys._is_immortal(result))
272+
254273
return result
255274

256275
for new in True, False:
257-
with self.subTest(new=new):
258-
self.assertEqual(resize(b'abc', 0, new), b'')
259-
self.assertEqual(resize(b'abc', 1, new), b'a')
260-
self.assertEqual(resize(b'abc', 2, new), b'ab')
261-
self.assertEqual(resize(b'abc', 3, new), b'abc')
262-
b = resize(b'abc', 4, new)
263-
self.assertEqual(len(b), 4)
264-
self.assertEqual(b[:3], b'abc')
265-
266-
self.assertEqual(resize(b'a', 0, new), b'')
267-
self.assertEqual(resize(b'a', 1, new), b'a')
268-
b = resize(b'a', 2, new)
269-
self.assertEqual(len(b), 2)
270-
self.assertEqual(b[:1], b'a')
271-
272-
self.assertEqual(resize(b'', 0, new), b'')
273-
self.assertEqual(len(resize(b'', 1, new)), 1)
274-
self.assertEqual(len(resize(b'', 2, new)), 2)
276+
for compute_hash in True, False:
277+
with self.subTest(new=new, compute_hash=compute_hash):
278+
self.assertEqual(resize(b'abc', 0, new, compute_hash), b'')
279+
self.assertEqual(resize(b'abc', 1, new, compute_hash), b'a')
280+
self.assertEqual(resize(b'abc', 2, new, compute_hash), b'ab')
281+
self.assertEqual(resize(b'abc', 3, new, compute_hash), b'abc')
282+
283+
b = resize(b'abc', 4, new, compute_hash)
284+
self.assertEqual(len(b), 4)
285+
self.assertEqual(b[:3], b'abc')
286+
287+
self.assertEqual(resize(b'a', 0, new, compute_hash), b'')
288+
self.assertEqual(resize(b'a', 1, new, compute_hash), b'a')
289+
b = resize(b'a', 2, new, compute_hash)
290+
self.assertEqual(len(b), 2)
291+
self.assertEqual(b[:1], b'a')
292+
293+
self.assertEqual(resize(b'', 0, new, compute_hash), b'')
294+
self.assertEqual(len(resize(b'', 1, new, compute_hash)), 1)
295+
self.assertEqual(len(resize(b'', 2, new, compute_hash)), 2)
275296

276297
self.assertRaises(SystemError, resize, b'abc', -1, False)
277298
self.assertRaises(SystemError, resize, bytearray(b'abc'), 3, False)

0 commit comments

Comments
 (0)