Skip to content

Commit 1bf9bab

Browse files
generatedunixname1734921407115435meta-codesync[bot]
authored andcommitted
Sync pre-release CPython main branch from GitHub (2026-08-05)
Summary: Imported python/cpython `3.16.0a0` from upstream rev [`ca4518b`](https://www.github.com/python/cpython/commit/ca4518b7f73bcd79d49ea20390f45827ddd0cb67) (committed 2026-08-05 04:43:08+00:00). # Commit Info - Base: (`3.16.0a0`) - [`fd9feab`](https://www.github.com/python/cpython/commit/fd9feabfb0fc80ee714b97140006d3af431d591a) (commit date: 2026-08-03 19:50:04+00:00) - Imported: (`3.16.0a0`) - [`ca4518b`](https://www.github.com/python/cpython/commit/ca4518b7f73bcd79d49ea20390f45827ddd0cb67) (commit date: 2026-08-05 04:43:08+00:00) # Noteworthy file changes - Low-signal files (5 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GODW6ChuKSTOtWoGABfjiJOI0rJQbr0LAAAz Differential Revision: D114837018 fbshipit-source-id: 1d7d910d770b62f14edf25e8a0aa478c04b08c41
1 parent 812f027 commit 1bf9bab

34 files changed

Lines changed: 440 additions & 107 deletions

Doc/library/asyncio-task.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,17 @@ Creating tasks
288288
# completion:
289289
task.add_done_callback(background_tasks.discard)
290290

291+
Note that this approach never awaits the tasks, so if a task
292+
fails, its exception is never retrieved and asyncio logs a
293+
"Task exception was never retrieved" message when the task is
294+
garbage collected. To avoid this, use :class:`asyncio.TaskGroup`
295+
which keeps a strong reference to each task, awaits them and
296+
propagates their exceptions::
297+
298+
async with asyncio.TaskGroup() as tg:
299+
for i in range(10):
300+
tg.create_task(some_coro(param=i))
301+
291302
.. versionadded:: 3.7
292303

293304
.. versionchanged:: 3.8

Lib/argparse.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,9 @@ def _prog_name(prog=None):
19641964
if modspec is None:
19651965
# simple script
19661966
return _os.path.basename(arg0)
1967+
if modspec.name != '__main__' and arg0 != modspec.origin:
1968+
# named module executed as main without altering sys.argv[0]
1969+
return _os.path.basename(arg0)
19671970
py = _os.path.basename(_sys.executable)
19681971
if modspec.name != '__main__':
19691972
# imported module or package

Lib/asyncio/windows_events.py

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,46 @@ def _get_accept_socket(self, family):
760760
s.settimeout(0)
761761
return s
762762

763+
def _process_completion_status(self, status):
764+
"""Process a single status from the completion port.
765+
766+
A caller that waits on the completion port itself can pass each
767+
status it receives here.
768+
"""
769+
err, transferred, key, address = status
770+
try:
771+
f, ov, obj, callback = self._cache.pop(address)
772+
except KeyError:
773+
if self._loop.get_debug():
774+
self._loop.call_exception_handler({
775+
'message': ('GetQueuedCompletionStatus() returned an '
776+
'unexpected event'),
777+
'status': ('err=%s transferred=%s key=%#x address=%#x'
778+
% (err, transferred, key, address)),
779+
})
780+
781+
# key is either zero, or it is used to return a pipe
782+
# handle which should be closed to avoid a leak.
783+
if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
784+
_winapi.CloseHandle(key)
785+
return
786+
787+
if obj in self._stopped_serving:
788+
f.cancel()
789+
# Don't call the callback if _register() already read the result or
790+
# if the overlapped has been cancelled
791+
elif not f.done():
792+
try:
793+
value = callback(transferred, key, ov)
794+
except OSError as e:
795+
f.set_exception(e)
796+
self._results.append(f)
797+
else:
798+
f.set_result(value)
799+
self._results.append(f)
800+
finally:
801+
f = None
802+
763803
def _poll(self, timeout=None):
764804
if timeout is None:
765805
ms = INFINITE
@@ -778,39 +818,8 @@ def _poll(self, timeout=None):
778818
break
779819
ms = 0
780820

781-
err, transferred, key, address = status
782-
try:
783-
f, ov, obj, callback = self._cache.pop(address)
784-
except KeyError:
785-
if self._loop.get_debug():
786-
self._loop.call_exception_handler({
787-
'message': ('GetQueuedCompletionStatus() returned an '
788-
'unexpected event'),
789-
'status': ('err=%s transferred=%s key=%#x address=%#x'
790-
% (err, transferred, key, address)),
791-
})
792-
793-
# key is either zero, or it is used to return a pipe
794-
# handle which should be closed to avoid a leak.
795-
if key not in (0, _overlapped.INVALID_HANDLE_VALUE):
796-
_winapi.CloseHandle(key)
797-
continue
798-
799-
if obj in self._stopped_serving:
800-
f.cancel()
801-
# Don't call the callback if _register() already read the result or
802-
# if the overlapped has been cancelled
803-
elif not f.done():
804-
try:
805-
value = callback(transferred, key, ov)
806-
except OSError as e:
807-
f.set_exception(e)
808-
self._results.append(f)
809-
else:
810-
f.set_result(value)
811-
self._results.append(f)
812-
finally:
813-
f = None
821+
# gh-154971: split out so custom event loops can call it directly
822+
self._process_completion_status(status)
814823

815824
# Remove unregistered futures
816825
for ov in self._unregistered:

Lib/mimetypes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def read(self, filename, strict=True):
250250
list of standard types, else to the list of non-standard
251251
types.
252252
"""
253-
with open(filename, encoding='utf-8') as fp:
253+
with open(filename, encoding='utf-8', errors='surrogateescape') as fp:
254254
self.readfp(fp, strict)
255255

256256
def readfp(self, fp, strict=True):
@@ -444,7 +444,7 @@ def init(files=None):
444444

445445
def read_mime_types(file):
446446
try:
447-
f = open(file, encoding='utf-8')
447+
f = open(file, encoding='utf-8', errors='surrogateescape')
448448
except OSError:
449449
return None
450450
with f:

Lib/test/_isolated_sample.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
a subprocess. Several of these tests fail, error or are skipped on purpose.
66
"""
77

8+
import atexit
9+
import os
810
import time
911
import unittest
1012
from test.support import isolation
@@ -109,3 +111,33 @@ class BrokenSubclassSample(SubclassingSample):
109111
@classmethod
110112
def setUpClass(cls):
111113
pass
114+
115+
116+
# The exit code the samples below die with, after their tests have run.
117+
EXIT_CODE = 3
118+
119+
120+
def _die_at_exit():
121+
atexit.register(os._exit, EXIT_CODE)
122+
123+
124+
class MethodExitSample(unittest.TestCase):
125+
126+
@isolation.runInSubprocess()
127+
def test_passes_then_dies(self):
128+
_die_at_exit()
129+
130+
@isolation.runInSubprocess()
131+
def test_fails_and_dies(self):
132+
_die_at_exit()
133+
self.fail('the test itself failed')
134+
135+
136+
@isolation.runInSubprocess()
137+
class ClassExitSample(unittest.TestCase):
138+
139+
def test_pass(self):
140+
pass
141+
142+
def test_dies(self):
143+
_die_at_exit()

Lib/test/list_tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from functools import cmp_to_key
77

88
from test import seq_tests
9-
from test.support import ALWAYS_EQ, NEVER_EQ, skip_if_huge_c_stack
9+
from test.support import ALWAYS_EQ, NEVER_EQ, run_with_limited_c_stack
1010
from test.support import skip_emscripten_stack_overflow, skip_wasi_stack_overflow
1111

1212

@@ -60,7 +60,7 @@ def test_repr(self):
6060
self.assertEqual(str(a2), "[0, 1, 2, [...], 3]")
6161
self.assertEqual(repr(a2), "[0, 1, 2, [...], 3]")
6262

63-
@skip_if_huge_c_stack(200_000)
63+
@run_with_limited_c_stack(200_000)
6464
@skip_wasi_stack_overflow()
6565
@skip_emscripten_stack_overflow()
6666
def test_repr_deep(self):

Lib/test/mapping_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ def __repr__(self):
629629
d = self._full_mapping({1: BadRepr()})
630630
self.assertRaises(Exc, repr, d)
631631

632-
@support.skip_if_huge_c_stack()
632+
@support.run_with_limited_c_stack()
633633
@support.skip_wasi_stack_overflow()
634634
@support.skip_emscripten_stack_overflow()
635635
@support.skip_if_sanitizer("requires deep stack", ub=True)

Lib/test/support/__init__.py

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"check_disallow_instantiation", "check_sanitizer", "skip_if_sanitizer",
4747
"requires_limited_api", "requires_specialization", "thread_unsafe",
4848
"skip_if_unlimited_stack_size", "skip_if_huge_c_stack",
49+
"run_with_limited_c_stack",
4950
# sys
5051
"MS_WINDOWS", "is_jython", "is_android", "is_emscripten", "is_wasi",
5152
"is_apple_mobile", "check_impl_detail", "unix_shell", "setswitchinterval",
@@ -2839,30 +2840,94 @@ def exceeds_recursion_limit():
28392840
return 150_000
28402841

28412842

2843+
def _has_huge_c_stack(depth):
2844+
"""Check that *depth* recursive calls cannot exhaust the C stack."""
2845+
try:
2846+
from _testinternalcapi import get_c_recursion_remaining
2847+
except ImportError:
2848+
# Fall back to checking for an unlimited stack size.
2849+
if is_emscripten or is_wasi or os.name == "nt":
2850+
return False
2851+
import resource
2852+
soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
2853+
return soft == hard and soft in (-1, 0xFFFF_FFFF_FFFF_FFFF)
2854+
else:
2855+
remaining = get_c_recursion_remaining()
2856+
# A negative value means integer overflow in the estimate
2857+
# (e.g. with an unlimited RLIMIT_STACK). The estimate is based on
2858+
# the size of the interpreter loop frame, so it is only a lower
2859+
# bound for recursion with smaller C frames.
2860+
return remaining >= depth or remaining < 0
2861+
2862+
28422863
def skip_if_huge_c_stack(depth=150_000):
28432864
"""Skip decorator for tests which cannot overflow the C stack.
28442865
28452866
Tests exhausting the C stack with *depth* recursive calls cannot
28462867
trigger the recursion protection if the C stack is too large (e.g.
28472868
with a large or unlimited RLIMIT_STACK), and either fail, or run
28482869
for a very long time, or crash, or consume all memory.
2870+
2871+
Prefer run_with_limited_c_stack() for tests recursing to a fixed depth.
28492872
"""
2850-
try:
2851-
from _testinternalcapi import get_c_recursion_remaining
2852-
except ImportError:
2853-
# Fall back to checking for an unlimited stack size.
2854-
huge = False
2855-
if not (is_emscripten or is_wasi) and os.name != "nt":
2856-
import resource
2857-
soft, hard = resource.getrlimit(resource.RLIMIT_STACK)
2858-
huge = soft == hard and soft in (-1, 0xFFFF_FFFF_FFFF_FFFF)
2859-
else:
2860-
remaining = get_c_recursion_remaining()
2861-
# A negative value means integer overflow in the estimate
2862-
# (e.g. with an unlimited RLIMIT_STACK).
2863-
huge = remaining >= depth or remaining < 0
28642873
return unittest.skipIf(
2865-
huge, f"the C stack is large enough for {depth} recursive calls")
2874+
_has_huge_c_stack(depth),
2875+
f"the C stack is large enough for {depth} recursive calls")
2876+
2877+
2878+
# Small enough to be exhausted by tens of thousands of recursive calls,
2879+
# but not smaller than Py_C_STACK_SIZE (4 MiB) which the interpreter
2880+
# assumes if it cannot query the thread stack size.
2881+
C_STACK_SIZE = 8 * 1024 * 1024
2882+
2883+
2884+
def run_with_limited_c_stack(depth=150_000, size=C_STACK_SIZE):
2885+
"""Decorator for tests exhausting the C stack with *depth* recursive calls.
2886+
2887+
Run the test in a separate thread with the C stack of *size* bytes, so
2888+
that the outcome does not depend on the C stack size of the main thread
2889+
(which can be large or unlimited, see RLIMIT_STACK).
2890+
2891+
If a thread with the limited C stack cannot be created, run the test in
2892+
the current thread, but skip it if the C stack is too large.
2893+
"""
2894+
reason = f"the C stack is large enough for {depth} recursive calls"
2895+
def decorator(test):
2896+
@functools.wraps(test)
2897+
def wrapper(*args, **kwargs):
2898+
def run_test():
2899+
# The C stack can still be too large if limiting it failed.
2900+
if _has_huge_c_stack(depth):
2901+
raise unittest.SkipTest(reason)
2902+
test(*args, **kwargs)
2903+
2904+
try:
2905+
import threading
2906+
old_size = threading.stack_size(size)
2907+
except (ImportError, ValueError, RuntimeError):
2908+
# Setting the thread stack size is not supported.
2909+
return run_test()
2910+
2911+
exceptions = []
2912+
def run():
2913+
try:
2914+
run_test()
2915+
except BaseException as exc:
2916+
exceptions.append(exc)
2917+
2918+
thread = threading.Thread(target=run)
2919+
try:
2920+
thread.start()
2921+
except RuntimeError:
2922+
# Threads are not supported.
2923+
return run_test()
2924+
finally:
2925+
threading.stack_size(old_size)
2926+
thread.join()
2927+
if exceptions:
2928+
raise exceptions[0]
2929+
return wrapper
2930+
return decorator
28662931

28672932

28682933
# Windows doesn't have os.uname() but it doesn't support s390x.

Lib/test/support/isolation.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ def _raise_fixture_outcome(outcome):
163163
raise exc from _remote(outcome['detail'])
164164

165165

166+
def _check_returncode(returncode, output, what):
167+
# The subprocess writes its result before exiting, so a non-zero exit code
168+
# means it died afterwards, during finalization, unnoticed by the result.
169+
if returncode:
170+
exc = _SubprocessTestError(
171+
f'the subprocess exited with code {returncode} '
172+
f'after running the {what}')
173+
raise exc from _remote(output)
174+
175+
166176
def _isolate_method(func):
167177
@functools.wraps(func)
168178
def wrapper(self, /, *args, **kwargs):
@@ -180,7 +190,9 @@ def wrapper(self, /, *args, **kwargs):
180190
raise exc from _remote(output)
181191
# The parent measures this method's own duration (the real cost of the
182192
# isolated run, subprocess startup included), so nothing to forward here.
193+
# Replay the outcomes first: a failure of the test itself is more useful.
183194
_replay_outcomes(self, payload['outcomes'])
195+
_check_returncode(returncode, output, 'test')
184196
return wrapper
185197

186198

@@ -219,13 +231,20 @@ def setUpClass(cls):
219231
by_id.setdefault(outcome['id'], []).append(outcome)
220232
cls._isolated_outcomes = by_id
221233
cls._isolated_durations = dict(payload.get('durations', ()))
234+
# Report the crash from tearDownClass(), after replaying the outcomes.
235+
cls._isolated_exit = (returncode, output)
222236

223237
def tearDownClass(cls):
224238
if runningInSubprocess:
225239
orig_tearDownClass(cls)
226-
else:
227-
cls._isolated_outcomes = None
228-
cls._isolated_durations = None
240+
return
241+
cls._isolated_outcomes = None
242+
cls._isolated_durations = None
243+
# Missing if an overriding setUpClass() bypassed the subprocess.
244+
exited = getattr(cls, '_isolated_exit', None)
245+
cls._isolated_exit = None
246+
if exited is not None:
247+
_check_returncode(*exited, 'class')
229248

230249
def _callSetUp(self):
231250
# In the parent the real test does not run, so neither should setUp().

Lib/test/test_argparse.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7346,6 +7346,19 @@ def test_module(self, compiled=False):
73467346
def test_module_compiled(self):
73477347
self.test_module(compiled=True)
73487348

7349+
def test_module_as_main_without_altering_argv(self):
7350+
basename = 'module' + os_helper.FS_NONASCII
7351+
modulename = f'{self.dirname}.{basename}'
7352+
self.make_script(self.dirname, basename)
7353+
runner_source = textwrap.dedent(f'''\
7354+
import runpy
7355+
runpy._run_module_as_main({modulename!r}, alter_argv=False)
7356+
''')
7357+
runner = script_helper.make_script(
7358+
self.dirname, 'runner', runner_source)
7359+
self.check_usage(os.path.basename(runner), runner,
7360+
PYTHONPATH=os.curdir)
7361+
73497362
def test_package(self, compiled=False):
73507363
basename = 'subpackage' + os_helper.FS_NONASCII
73517364
packagename = f'{self.dirname}.{basename}'

0 commit comments

Comments
 (0)