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
8 changes: 4 additions & 4 deletions Doc/library/gc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ The :mod:`gc` module provides the following functions:
The effect of calling ``gc.collect()`` while the interpreter is already
performing a collection is undefined.

.. versionchanged:: 3.13
.. versionchanged:: 3.14
``generation=1`` performs an increment of collection.


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

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

.. versionchanged:: 3.8
New *generation* parameter.

.. versionchanged:: 3.13
.. versionchanged:: 3.14
Generation 1 is removed

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

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

.. versionchanged:: 3.13
.. versionchanged:: 3.14
*threshold2* is ignored


Expand Down
71 changes: 71 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,30 @@ free-threaded build and false for the GIL-enabled build.

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

.. _whatsnew314-incremental-gc:

Incremental garbage collection
------------------------------

The cycle garbage collector is now incremental.
This means that maximum pause times are reduced
by an order of magnitude or more for larger heaps.

There are now only two generations: young and old.
When :func:`gc.collect` is not called directly, the
GC is invoked a little less frequently. When invoked, it
collects the young generation and an increment of the
old generation, instead of collecting one or more generations.

The behavior of :func:`!gc.collect` changes slightly:

* ``gc.collect(1)``: Performs an increment of garbage collection,
rather than collecting generation 1.
* Other calls to :func:`!gc.collect` are unchanged.

(Contributed by Mark Shannon in :gh:`108362`.)


Other language changes
======================

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


gc
--

The cyclic garbage collector is now incremental,
which changes the meaning of the results of
:meth:`~gc.get_threshold` and :meth:`~gc.set_threshold`
as well as :meth:`~gc.get_count` and :meth:`~gc.get_stats`.

* For backwards compatibility, :meth:`~gc.get_threshold` continues to return
a three-item tuple.
The first value is the threshold for young collections, as before;
the second value determines the rate at which the old collection is scanned
(the default is 10, and higher values mean that the old collection
is scanned more slowly).
The third value is meaningless and is always zero.

* :meth:`~gc.set_threshold` ignores any items after the second.

* :meth:`~gc.get_count` and :meth:`~gc.get_stats` continue to return
the same format of results.
The only difference is that instead of the results referring to
the young, aging and old generations,
the results refer to the young generation
and the aging and collecting spaces of the old generation.

In summary, code that attempted to manipulate the behavior of the cycle GC
may not work exactly as intended, but it is very unlikely to be harmful.
All other code will work just fine.


getopt
------

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


base64
------

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


gc
--

* The new :ref:`incremental garbage collector <whatsnew314-incremental-gc>`
means that maximum pause times are reduced
by an order of magnitude or more for larger heaps.
(Contributed by Mark Shannon in :gh:`108362`.)


io
---
* :mod:`io` which provides the built-in :func:`open` makes less system calls
Expand Down Expand Up @@ -2707,6 +2771,13 @@ Changes in the Python API
Wrap it in :func:`staticmethod` if you want to preserve the old behavior.
(Contributed by Serhiy Storchaka and Dominykas Grigonis in :gh:`121027`.)

* The :ref:`garbage collector is now incremental <whatsnew314-incremental-gc>`,
which means that the behavior of :func:`gc.collect` changes slightly:

* ``gc.collect(1)``: Performs an increment of garbage collection,
rather than collecting generation 1.
* Other calls to :func:`!gc.collect` are unchanged.

* The :func:`locale.nl_langinfo` function now sets temporarily the ``LC_CTYPE``
locale in some cases.
This temporary change affects other threads.
Expand Down
12 changes: 11 additions & 1 deletion Lib/hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
}

def __get_builtin_constructor(name):
if not isinstance(name, str):
# Since this function is only used by new(), we use the same
# exception as _hashlib.new() when 'name' is of incorrect type.
err = f"new() argument 'name' must be str, not {type(name).__name__}"
raise TypeError(err)
cache = __builtin_constructor_cache
constructor = cache.get(name)
if constructor is not None:
Expand Down Expand Up @@ -120,10 +125,13 @@ def __get_builtin_constructor(name):
if constructor is not None:
return constructor

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


def __get_openssl_constructor(name):
# This function is only used until the module has been initialized.
assert isinstance(name, str), "invalid call to __get_openssl_constructor()"
if name in __block_openssl_constructor:
# Prefer our builtin blake2 implementation.
return __get_builtin_constructor(name)
Expand Down Expand Up @@ -154,6 +162,8 @@ def __hash_new(name, *args, **kwargs):
optionally initialized with data (which must be a bytes-like object).
"""
if name in __block_openssl_constructor:
# __block_openssl_constructor is expected to contain strings only
assert isinstance(name, str), f"unexpected name: {name}"
# Prefer our builtin blake2 implementation.
return __get_builtin_constructor(name)(*args, **kwargs)
try:
Expand Down
14 changes: 14 additions & 0 deletions Lib/hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
digest_size = None


def _is_shake_constructor(digest_like):
if isinstance(digest_like, str):
name = digest_like
else:
h = digest_like() if callable(digest_like) else digest_like.new()
if not isinstance(name := getattr(h, "name", None), str):
return False
return name.startswith(("shake", "SHAKE"))


def _get_digest_constructor(digest_like):
if callable(digest_like):
return digest_like
Expand Down Expand Up @@ -109,6 +119,8 @@ def _init_old(self, key, msg, digestmod):
import warnings

digest_cons = _get_digest_constructor(digestmod)
if _is_shake_constructor(digest_cons):
raise ValueError(f"unsupported hash algorithm {digestmod}")

self._hmac = None
self._outer = digest_cons()
Expand Down Expand Up @@ -243,6 +255,8 @@ def digest(key, msg, digest):

def _compute_digest_fallback(key, msg, digest):
digest_cons = _get_digest_constructor(digest)
if _is_shake_constructor(digest_cons):
raise ValueError(f"unsupported hash algorithm {digest}")
inner = digest_cons()
outer = digest_cons()
blocksize = getattr(inner, 'block_size', 64)
Expand Down
8 changes: 6 additions & 2 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,9 @@ def test_clinic_signature_errors(self):

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

def test_new_upper_to_lower(self):
self.assertEqual(hashlib.new("SHA256").name, "sha256")
Expand All @@ -370,7 +372,9 @@ def test_get_builtin_constructor(self):
sys.modules['_md5'] = _md5
else:
del sys.modules['_md5']
self.assertRaises(TypeError, get_builtin_constructor, 3)
# ensure that the exception message remains consistent
err = re.escape("new() argument 'name' must be str, not int")
self.assertRaises(TypeError, err, get_builtin_constructor, 3)
constructor = get_builtin_constructor('md5')
self.assertIs(constructor, _md5.md5)
self.assertEqual(sorted(builtin_constructor_cache), ['MD5', 'md5'])
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,7 +960,7 @@ def raiser():
with self.assertRaisesRegex(RuntimeError, "custom exception"):
func(b'key', b'msg', raiser)

with self.assertRaisesRegex(ValueError, 'hash type'):
with self.assertRaisesRegex(ValueError, 'unsupported hash algorithm'):
func(b'key', b'msg', 'unknown')

with self.assertRaisesRegex(AttributeError, 'new'):
Expand Down
53 changes: 33 additions & 20 deletions Lib/test/test_remote_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import unittest
import unittest.mock
from contextlib import closing, contextmanager, redirect_stdout, redirect_stderr, ExitStack
from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT
from test.support import is_wasi, cpython_only, force_color, requires_subprocess, SHORT_TIMEOUT, subTests
from test.support.os_helper import TESTFN, unlink
from typing import List

Expand Down Expand Up @@ -279,37 +279,50 @@ def test_handling_other_message(self):
expected_stdout="Some message.\n",
)

def test_handling_help_for_command(self):
"""Test handling a request to display help for a command."""
@unittest.skipIf(sys.flags.optimize >= 2, "Help not available for -OO")
@subTests(
"help_request,expected_substring",
[
# a request to display help for a command
({"help": "ll"}, "Usage: ll | longlist"),
# a request to display a help overview
({"help": ""}, "type help <topic>"),
# a request to display the full PDB manual
({"help": "pdb"}, ">>> import pdb"),
],
)
def test_handling_help_when_available(self, help_request, expected_substring):
"""Test handling help requests when help is available."""
incoming = [
("server", {"help": "ll"}),
("server", help_request),
]
self.do_test(
incoming=incoming,
expected_outgoing=[],
expected_stdout_substring="Usage: ll | longlist",
expected_stdout_substring=expected_substring,
)

def test_handling_help_without_a_specific_topic(self):
"""Test handling a request to display a help overview."""
@unittest.skipIf(sys.flags.optimize < 2, "Needs -OO")
@subTests(
"help_request,expected_substring",
[
# a request to display help for a command
({"help": "ll"}, "No help for 'll'"),
# a request to display a help overview
({"help": ""}, "Undocumented commands"),
# a request to display the full PDB manual
({"help": "pdb"}, "No help for 'pdb'"),
],
)
def test_handling_help_when_not_available(self, help_request, expected_substring):
"""Test handling help requests when help is not available."""
incoming = [
("server", {"help": ""}),
("server", help_request),
]
self.do_test(
incoming=incoming,
expected_outgoing=[],
expected_stdout_substring="type help <topic>",
)

def test_handling_help_pdb(self):
"""Test handling a request to display the full PDB manual."""
incoming = [
("server", {"help": "pdb"}),
]
self.do_test(
incoming=incoming,
expected_outgoing=[],
expected_stdout_substring=">>> import pdb",
expected_stdout_substring=expected_substring,
)

def test_handling_pdb_prompts(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:mod:`hashlib`: improve exception messages when a hash algorithm is not
recognized, blocked by the current security policy or incompatible with
the desired operation (for instance, using HMAC with SHAKE).
Patch by Bénédikt Tran.
Loading
Loading