Skip to content

Commit a9cd436

Browse files
authored
Merge branch 'main' into bpo-28879
2 parents c9e2af2 + dda9d00 commit a9cd436

File tree

11 files changed

+306
-72
lines changed

11 files changed

+306
-72
lines changed

Doc/library/gc.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ The :mod:`gc` module provides the following functions:
6060
The effect of calling ``gc.collect()`` while the interpreter is already
6161
performing a collection is undefined.
6262

63-
.. versionchanged:: 3.13
63+
.. versionchanged:: 3.14
6464
``generation=1`` performs an increment of collection.
6565

6666

@@ -83,13 +83,13 @@ The :mod:`gc` module provides the following functions:
8383
returned. If *generation* is not ``None``, return only the objects as follows:
8484

8585
* 0: All objects in the young generation
86-
* 1: No objects, as there is no generation 1 (as of Python 3.13)
86+
* 1: No objects, as there is no generation 1 (as of Python 3.14)
8787
* 2: All objects in the old generation
8888

8989
.. versionchanged:: 3.8
9090
New *generation* parameter.
9191

92-
.. versionchanged:: 3.13
92+
.. versionchanged:: 3.14
9393
Generation 1 is removed
9494

9595
.. audit-event:: gc.get_objects generation gc.get_objects
@@ -142,7 +142,7 @@ The :mod:`gc` module provides the following functions:
142142

143143
See `Garbage collector design <https://devguide.python.org/garbage_collector>`_ for more information.
144144

145-
.. versionchanged:: 3.13
145+
.. versionchanged:: 3.14
146146
*threshold2* is ignored
147147

148148

Doc/whatsnew/3.14.rst

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,30 @@ free-threaded build and false for the GIL-enabled build.
10581058

10591059
(Contributed by Neil Schemenauer and Kumar Aditya in :gh:`130010`.)
10601060

1061+
.. _whatsnew314-incremental-gc:
1062+
1063+
Incremental garbage collection
1064+
------------------------------
1065+
1066+
The cycle garbage collector is now incremental.
1067+
This means that maximum pause times are reduced
1068+
by an order of magnitude or more for larger heaps.
1069+
1070+
There are now only two generations: young and old.
1071+
When :func:`gc.collect` is not called directly, the
1072+
GC is invoked a little less frequently. When invoked, it
1073+
collects the young generation and an increment of the
1074+
old generation, instead of collecting one or more generations.
1075+
1076+
The behavior of :func:`!gc.collect` changes slightly:
1077+
1078+
* ``gc.collect(1)``: Performs an increment of garbage collection,
1079+
rather than collecting generation 1.
1080+
* Other calls to :func:`!gc.collect` are unchanged.
1081+
1082+
(Contributed by Mark Shannon in :gh:`108362`.)
1083+
1084+
10611085
Other language changes
10621086
======================
10631087

@@ -1486,6 +1510,36 @@ functools
14861510
(Contributed by Sayandip Dutta in :gh:`125916`.)
14871511

14881512

1513+
gc
1514+
--
1515+
1516+
The cyclic garbage collector is now incremental,
1517+
which changes the meaning of the results of
1518+
:meth:`~gc.get_threshold` and :meth:`~gc.set_threshold`
1519+
as well as :meth:`~gc.get_count` and :meth:`~gc.get_stats`.
1520+
1521+
* For backwards compatibility, :meth:`~gc.get_threshold` continues to return
1522+
a three-item tuple.
1523+
The first value is the threshold for young collections, as before;
1524+
the second value determines the rate at which the old collection is scanned
1525+
(the default is 10, and higher values mean that the old collection
1526+
is scanned more slowly).
1527+
The third value is meaningless and is always zero.
1528+
1529+
* :meth:`~gc.set_threshold` ignores any items after the second.
1530+
1531+
* :meth:`~gc.get_count` and :meth:`~gc.get_stats` continue to return
1532+
the same format of results.
1533+
The only difference is that instead of the results referring to
1534+
the young, aging and old generations,
1535+
the results refer to the young generation
1536+
and the aging and collecting spaces of the old generation.
1537+
1538+
In summary, code that attempted to manipulate the behavior of the cycle GC
1539+
may not work exactly as intended, but it is very unlikely to be harmful.
1540+
All other code will work just fine.
1541+
1542+
14891543
getopt
14901544
------
14911545

@@ -2233,6 +2287,7 @@ asyncio
22332287
(Contributed by Yury Selivanov, Pablo Galindo Salgado, and Łukasz Langa
22342288
in :gh:`91048`.)
22352289

2290+
22362291
base64
22372292
------
22382293

@@ -2241,6 +2296,15 @@ base64
22412296
(Contributed by Bénédikt Tran, Chris Markiewicz, and Adam Turner in :gh:`118761`.)
22422297

22432298

2299+
gc
2300+
--
2301+
2302+
* The new :ref:`incremental garbage collector <whatsnew314-incremental-gc>`
2303+
means that maximum pause times are reduced
2304+
by an order of magnitude or more for larger heaps.
2305+
(Contributed by Mark Shannon in :gh:`108362`.)
2306+
2307+
22442308
io
22452309
---
22462310
* :mod:`io` which provides the built-in :func:`open` makes less system calls
@@ -2707,6 +2771,13 @@ Changes in the Python API
27072771
Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
27082772
(Contributed by Serhiy Storchaka and Dominykas Grigonis in :gh:`121027`.)
27092773

2774+
* The :ref:`garbage collector is now incremental <whatsnew314-incremental-gc>`,
2775+
which means that the behavior of :func:`gc.collect` changes slightly:
2776+
2777+
* ``gc.collect(1)``: Performs an increment of garbage collection,
2778+
rather than collecting generation 1.
2779+
* Other calls to :func:`!gc.collect` are unchanged.
2780+
27102781
* The :func:`locale.nl_langinfo` function now sets temporarily the ``LC_CTYPE``
27112782
locale in some cases.
27122783
This temporary change affects other threads.

Lib/hashlib.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@
8080
}
8181

8282
def __get_builtin_constructor(name):
83+
if not isinstance(name, str):
84+
# Since this function is only used by new(), we use the same
85+
# exception as _hashlib.new() when 'name' is of incorrect type.
86+
err = f"new() argument 'name' must be str, not {type(name).__name__}"
87+
raise TypeError(err)
8388
cache = __builtin_constructor_cache
8489
constructor = cache.get(name)
8590
if constructor is not None:
@@ -120,10 +125,13 @@ def __get_builtin_constructor(name):
120125
if constructor is not None:
121126
return constructor
122127

123-
raise ValueError('unsupported hash type ' + name)
128+
# Keep the message in sync with hashlib.h::HASHLIB_UNSUPPORTED_ALGORITHM.
129+
raise ValueError(f'unsupported hash algorithm {name}')
124130

125131

126132
def __get_openssl_constructor(name):
133+
# This function is only used until the module has been initialized.
134+
assert isinstance(name, str), "invalid call to __get_openssl_constructor()"
127135
if name in __block_openssl_constructor:
128136
# Prefer our builtin blake2 implementation.
129137
return __get_builtin_constructor(name)
@@ -154,6 +162,8 @@ def __hash_new(name, *args, **kwargs):
154162
optionally initialized with data (which must be a bytes-like object).
155163
"""
156164
if name in __block_openssl_constructor:
165+
# __block_openssl_constructor is expected to contain strings only
166+
assert isinstance(name, str), f"unexpected name: {name}"
157167
# Prefer our builtin blake2 implementation.
158168
return __get_builtin_constructor(name)(*args, **kwargs)
159169
try:

Lib/hmac.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626
digest_size = None
2727

2828

29+
def _is_shake_constructor(digest_like):
30+
if isinstance(digest_like, str):
31+
name = digest_like
32+
else:
33+
h = digest_like() if callable(digest_like) else digest_like.new()
34+
if not isinstance(name := getattr(h, "name", None), str):
35+
return False
36+
return name.startswith(("shake", "SHAKE"))
37+
38+
2939
def _get_digest_constructor(digest_like):
3040
if callable(digest_like):
3141
return digest_like
@@ -109,6 +119,8 @@ def _init_old(self, key, msg, digestmod):
109119
import warnings
110120

111121
digest_cons = _get_digest_constructor(digestmod)
122+
if _is_shake_constructor(digest_cons):
123+
raise ValueError(f"unsupported hash algorithm {digestmod}")
112124

113125
self._hmac = None
114126
self._outer = digest_cons()
@@ -243,6 +255,8 @@ def digest(key, msg, digest):
243255

244256
def _compute_digest_fallback(key, msg, digest):
245257
digest_cons = _get_digest_constructor(digest)
258+
if _is_shake_constructor(digest_cons):
259+
raise ValueError(f"unsupported hash algorithm {digest}")
246260
inner = digest_cons()
247261
outer = digest_cons()
248262
blocksize = getattr(inner, 'block_size', 64)

Lib/test/test_hashlib.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,9 @@ def test_clinic_signature_errors(self):
343343

344344
def test_unknown_hash(self):
345345
self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam')
346-
self.assertRaises(TypeError, hashlib.new, 1)
346+
# ensure that the exception message remains consistent
347+
err = re.escape("new() argument 'name' must be str, not int")
348+
self.assertRaisesRegex(TypeError, err, hashlib.new, 1)
347349

348350
def test_new_upper_to_lower(self):
349351
self.assertEqual(hashlib.new("SHA256").name, "sha256")
@@ -370,7 +372,9 @@ def test_get_builtin_constructor(self):
370372
sys.modules['_md5'] = _md5
371373
else:
372374
del sys.modules['_md5']
373-
self.assertRaises(TypeError, get_builtin_constructor, 3)
375+
# ensure that the exception message remains consistent
376+
err = re.escape("new() argument 'name' must be str, not int")
377+
self.assertRaises(TypeError, err, get_builtin_constructor, 3)
374378
constructor = get_builtin_constructor('md5')
375379
self.assertIs(constructor, _md5.md5)
376380
self.assertEqual(sorted(builtin_constructor_cache), ['MD5', 'md5'])

Lib/test/test_hmac.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,7 +960,7 @@ def raiser():
960960
with self.assertRaisesRegex(RuntimeError, "custom exception"):
961961
func(b'key', b'msg', raiser)
962962

963-
with self.assertRaisesRegex(ValueError, 'hash type'):
963+
with self.assertRaisesRegex(ValueError, 'unsupported hash algorithm'):
964964
func(b'key', b'msg', 'unknown')
965965

966966
with self.assertRaisesRegex(AttributeError, 'new'):

Lib/test/test_remote_pdb.py

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import unittest
1212
import unittest.mock
1313
from contextlib import closing, contextmanager, redirect_stdout, redirect_stderr, ExitStack
14-
from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT
14+
from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT, subTests
1515
from test.support.os_helper import TESTFN, unlink
1616
from typing import List
1717

@@ -279,37 +279,50 @@ def test_handling_other_message(self):
279279
expected_stdout="Some message.\n",
280280
)
281281

282-
def test_handling_help_for_command(self):
283-
"""Test handling a request to display help for a command."""
282+
@unittest.skipIf(sys.flags.optimize >= 2, "Help not available for -OO")
283+
@subTests(
284+
"help_request,expected_substring",
285+
[
286+
# a request to display help for a command
287+
({"help": "ll"}, "Usage: ll | longlist"),
288+
# a request to display a help overview
289+
({"help": ""}, "type help <topic>"),
290+
# a request to display the full PDB manual
291+
({"help": "pdb"}, ">>> import pdb"),
292+
],
293+
)
294+
def test_handling_help_when_available(self, help_request, expected_substring):
295+
"""Test handling help requests when help is available."""
284296
incoming = [
285-
("server", {"help": "ll"}),
297+
("server", help_request),
286298
]
287299
self.do_test(
288300
incoming=incoming,
289301
expected_outgoing=[],
290-
expected_stdout_substring="Usage: ll | longlist",
302+
expected_stdout_substring=expected_substring,
291303
)
292304

293-
def test_handling_help_without_a_specific_topic(self):
294-
"""Test handling a request to display a help overview."""
305+
@unittest.skipIf(sys.flags.optimize < 2, "Needs -OO")
306+
@subTests(
307+
"help_request,expected_substring",
308+
[
309+
# a request to display help for a command
310+
({"help": "ll"}, "No help for 'll'"),
311+
# a request to display a help overview
312+
({"help": ""}, "Undocumented commands"),
313+
# a request to display the full PDB manual
314+
({"help": "pdb"}, "No help for 'pdb'"),
315+
],
316+
)
317+
def test_handling_help_when_not_available(self, help_request, expected_substring):
318+
"""Test handling help requests when help is not available."""
295319
incoming = [
296-
("server", {"help": ""}),
320+
("server", help_request),
297321
]
298322
self.do_test(
299323
incoming=incoming,
300324
expected_outgoing=[],
301-
expected_stdout_substring="type help <topic>",
302-
)
303-
304-
def test_handling_help_pdb(self):
305-
"""Test handling a request to display the full PDB manual."""
306-
incoming = [
307-
("server", {"help": "pdb"}),
308-
]
309-
self.do_test(
310-
incoming=incoming,
311-
expected_outgoing=[],
312-
expected_stdout_substring=">>> import pdb",
325+
expected_stdout_substring=expected_substring,
313326
)
314327

315328
def test_handling_pdb_prompts(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`hashlib`: improve exception messages when a hash algorithm is not
2+
recognized, blocked by the current security policy or incompatible with
3+
the desired operation (for instance, using HMAC with SHAKE).
4+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)