Skip to content

Commit 9c65f1a

Browse files
generatedunixname1734921407115435meta-codesync[bot]
authored andcommitted
Sync pre-release CPython main branch from GitHub (2026-08-16)
Summary: Imported python/cpython `3.16.0a0` from upstream rev [`70b2b0a`](https://www.github.com/python/cpython/commit/70b2b0a2dcce71e8b94757b8ed9313b0cbe0fc4f) (committed 2026-08-16 04:47:16+00:00). # Commit Info - Base: (`3.16.0a0`) - [`fbd2e01`](https://www.github.com/python/cpython/commit/fbd2e015d462870b9a48bb5e6628af6c372ed1d5) (commit date: 2026-08-15 04:26:32+00:00) - Imported: (`3.16.0a0`) - [`70b2b0a`](https://www.github.com/python/cpython/commit/70b2b0a2dcce71e8b94757b8ed9313b0cbe0fc4f) (commit date: 2026-08-16 04:47:16+00:00) # Noteworthy file changes - Test files (1 added) - Low-signal files (7 added) (NEWS.d, docs, .github) Complete list of added/removed files: https://www.internalfb.com/intern/everpaste/?color=0&handle=GND8NS4dKbljErcFAL3gFL11738Mbr0LAAAz Differential Revision: D116191988 fbshipit-source-id: 0109ae1d1621f6363cf404fe355ddd8f03a53316
1 parent e96b7cc commit 9c65f1a

24 files changed

Lines changed: 475 additions & 93 deletions

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,12 @@ Include/internal/pycore_ast_state.h generated
8080
Include/internal/pycore_opcode.h generated
8181
Include/internal/pycore_opcode_metadata.h generated
8282
Include/internal/pycore_*_generated.h generated
83+
Include/internal/pycore_token.h generated
8384
Include/internal/pycore_uop_ids.h generated
8485
Include/internal/pycore_uop_metadata.h generated
8586
Include/opcode.h generated
8687
Include/opcode_ids.h generated
8788
Include/slots_generated.h generated
88-
Include/token.h generated
8989
Lib/_opcode_metadata.py generated
9090
Lib/idlelib/help.html generated
9191
Lib/keyword.py generated

Doc/library/venv.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ mechanisms for third-party virtual environment creators to customize environment
302302
creation according to their needs, the :class:`EnvBuilder` class.
303303

304304
.. class:: EnvBuilder(system_site_packages=False, clear=False, \
305-
symlinks=False, upgrade=False, with_pip=False, \
305+
symlinks=None, upgrade=False, with_pip=False, \
306306
prompt=None, upgrade_deps=False, \
307307
*, scm_ignore_files=frozenset())
308308
@@ -316,7 +316,8 @@ creation according to their needs, the :class:`EnvBuilder` class.
316316
any existing target directory, before creating the environment.
317317

318318
* *symlinks* -- a boolean value indicating whether to attempt to symlink the
319-
Python binary rather than copying.
319+
Python binary rather than copying. If ``None``, the default is ``False`` on
320+
Windows and ``True`` on other platforms, matching the :ref:`CLI <venv-cli>`.
320321

321322
* *upgrade* -- a boolean value which, if true, will upgrade an existing
322323
environment with the running Python - for use when that Python has been
@@ -351,6 +352,9 @@ creation according to their needs, the :class:`EnvBuilder` class.
351352
.. versionchanged:: 3.13
352353
Added the ``scm_ignore_files`` parameter
353354

355+
.. versionchanged:: 3.16
356+
The default value of *symlinks* is now platform-dependent.
357+
354358
:class:`EnvBuilder` may be used as a base class.
355359

356360
.. method:: create(env_dir)
@@ -521,7 +525,7 @@ creation according to their needs, the :class:`EnvBuilder` class.
521525
There is also a module-level convenience function:
522526

523527
.. function:: create(env_dir, system_site_packages=False, clear=False, \
524-
symlinks=False, with_pip=False, prompt=None, \
528+
symlinks=None, with_pip=False, prompt=None, \
525529
upgrade_deps=False, *, scm_ignore_files=frozenset())
526530
527531
Create an :class:`EnvBuilder` with the given keyword arguments, and call its
@@ -541,6 +545,9 @@ There is also a module-level convenience function:
541545
.. versionchanged:: 3.13
542546
Added the *scm_ignore_files* parameter
543547

548+
.. versionchanged:: 3.16
549+
The default value of *symlinks* is now platform-dependent.
550+
544551
An example of extending ``EnvBuilder``
545552
--------------------------------------
546553

Lib/dbm/dumb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ def reorganize(self):
311311
reorganize_pos += blocks_occupied * _BLOCKSIZE
312312

313313
f.truncate(reorganize_pos)
314+
self._modified = True
314315
# Commit changes to index, which were not in-place.
315316
self._commit()
316317

Lib/tarfile.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,17 +1613,22 @@ def _proc_pax(self, tarfile):
16131613
if self.type in (XHDTYPE, SOLARIS_XHDTYPE):
16141614
# Patch the TarInfo object with the extended header info.
16151615
next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors)
1616-
next.offset = self.offset
16171616

16181617
if "size" in pax_headers:
16191618
# If the extended header replaces the size field,
16201619
# we need to recalculate the offset where the next
16211620
# header starts.
1622-
offset = next.offset_data
1621+
offset = next.offset + BLOCKSIZE
16231622
if next.isreg() or next.type not in SUPPORTED_TYPES:
1624-
offset += next._block(next.size)
1623+
try:
1624+
size = PAX_NUMBER_FIELDS["size"](pax_headers["size"])
1625+
except ValueError:
1626+
size = 0
1627+
offset += next._block(size)
16251628
tarfile.offset = offset
16261629

1630+
next.offset = self.offset
1631+
16271632
return next
16281633

16291634
def _proc_gnusparse_00(self, next, raw_headers):

Lib/test/test_dbm_dumb.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ def test_write_write_read(self):
114114
with contextlib.closing(dumbdbm.open(_fname)) as f:
115115
self.assertEqual(f[b'1'], b'hello2')
116116

117+
def test_reorganize_persists_changed_offsets(self):
118+
with dumbdbm.open(_fname, 'n') as f:
119+
f[b'deleted'] = b'x'
120+
f[b'retained'] = b'value'
121+
del f[b'deleted']
122+
f.reorganize()
123+
124+
with dumbdbm.open(_fname, 'r') as f:
125+
self.assertEqual(f[b'retained'], b'value')
126+
117127
def test_str_read(self):
118128
self.init_db()
119129
with contextlib.closing(dumbdbm.open(_fname, 'r')) as f:
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import contextvars
2+
import unittest
3+
from threading import Event, Thread
4+
5+
from test.support import threading_helper
6+
7+
8+
@threading_helper.requires_working_threading()
9+
class TestContext(unittest.TestCase):
10+
def test_racing_read_write(self):
11+
# gh-154535: reading a Context object from one thread while another
12+
# thread sets variables in it used to crash. The readers looked at
13+
# Context.ctx_vars without owning a reference to it, so the writer
14+
# could deallocate the mapping while a reader was walking it.
15+
ctx = contextvars.Context()
16+
cvars = [contextvars.ContextVar(f"cvar{i}") for i in range(64)]
17+
done = Event()
18+
errors = []
19+
20+
def writer():
21+
def body():
22+
i = 0
23+
while not done.is_set():
24+
cvars[i % len(cvars)].set(i)
25+
i += 1
26+
try:
27+
ctx.run(body)
28+
except BaseException as e:
29+
errors.append(e)
30+
31+
def reader():
32+
try:
33+
for _ in range(200):
34+
ctx.copy()
35+
len(ctx)
36+
list(ctx)
37+
list(ctx.items())
38+
list(ctx.keys())
39+
list(ctx.values())
40+
cvars[0] in ctx
41+
ctx.get(cvars[0])
42+
ctx == ctx
43+
except BaseException as e:
44+
errors.append(e)
45+
finally:
46+
done.set()
47+
48+
threads = [Thread(target=writer)]
49+
threads += [Thread(target=reader) for _ in range(4)]
50+
with threading_helper.start_threads(threads, done.set):
51+
pass
52+
53+
self.assertEqual(errors, [], msg=f"unexpected errors: {errors}")
54+
55+
56+
if __name__ == "__main__":
57+
unittest.main()

Lib/test/test_free_threading/test_type.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,5 +324,63 @@ def wrapper():
324324
for reader in readers:
325325
reader.join()
326326

327+
def test_concurrent_setattr_deadlock(self):
328+
# gh-155400: two threads assigning to a special method of the same
329+
# class could deadlock. One thread held the type lock and waited for
330+
# the type dict mutex, which its critical section had released when it
331+
# blocked on the stop-the-world mutex, while the other held the type
332+
# dict mutex and waited for the type lock.
333+
# This is fairly difficult to trigger the race but this N seems to do
334+
# it at least sometimes.
335+
N = 200
336+
done = False
337+
338+
class Base:
339+
pass
340+
341+
def setter():
342+
func = lambda self: "x"
343+
barrier.wait()
344+
while not done:
345+
Base.__repr__ = func
346+
try:
347+
del Base.__repr__
348+
except AttributeError:
349+
pass
350+
351+
def subclasser():
352+
barrier.wait()
353+
while not done:
354+
type('Sub', (Base,), {})()
355+
356+
def lister():
357+
barrier.wait()
358+
while not done:
359+
Base.__subclasses__()
360+
361+
def basesetter():
362+
nonlocal done
363+
barrier.wait()
364+
for _ in range(N):
365+
class A:
366+
pass
367+
class C:
368+
pass
369+
class B(A):
370+
pass
371+
B.__bases__ = (C,)
372+
done = True
373+
374+
# The setter threads are the ones that deadlock. The others are there
375+
# to keep the type lock and the stop-the-world mutex contended, which
376+
# is what gets the setters into the window where it happens.
377+
targets = (setter, setter, subclasser, subclasser,
378+
lister, lister, basesetter)
379+
barrier = threading.Barrier(len(targets))
380+
threads = [Thread(target=target) for target in targets]
381+
with threading_helper.start_threads(threads):
382+
pass
383+
384+
327385
if __name__ == "__main__":
328386
unittest.main()

Lib/test/test_hmac.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,19 +1459,25 @@ class OperatorCompareDigestTestCase(CompareDigestMixin, unittest.TestCase):
14591459
class PyMiscellaneousTests(unittest.TestCase):
14601460
"""Miscellaneous tests for the pure Python HMAC module."""
14611461

1462+
@staticmethod
1463+
def mock__compute_digest_fallback(hmac):
1464+
fn = getattr(hmac, meth := "_compute_digest_fallback")
1465+
return patch.object(hmac, meth, wraps=fn)
1466+
1467+
@staticmethod
1468+
def mock_HMAC_method(hmac, method):
1469+
fn = getattr(hmac.HMAC, method)
1470+
return patch.object(hmac.HMAC, method, autospec=True, wraps=fn)
1471+
14621472
@hashlib_helper.requires_builtin_hmac()
14631473
def test_hmac_constructor_uses_builtin(self):
14641474
# Block the OpenSSL implementation and check that
14651475
# HMAC() uses the built-in implementation instead.
14661476
hmac = import_fresh_module("hmac", blocked=["_hashlib"])
14671477

1468-
def watch_method(cls, name):
1469-
wraps = getattr(cls, name)
1470-
return patch.object(cls, name, autospec=True, wraps=wraps)
1471-
14721478
with (
1473-
watch_method(hmac.HMAC, '_init_openssl_hmac') as f,
1474-
watch_method(hmac.HMAC, '_init_builtin_hmac') as g,
1479+
self.mock_HMAC_method(hmac, '_init_openssl_hmac') as f,
1480+
self.mock_HMAC_method(hmac, '_init_builtin_hmac') as g,
14751481
):
14761482
_ = hmac.HMAC(b'key', b'msg', digestmod="sha256")
14771483
f.assert_not_called()
@@ -1542,11 +1548,11 @@ def do_test_hmac_digest_overflow_error_switch_to_slow(self, hmac, size):
15421548
bigkey = b'K' * size
15431549
bigmsg = b'M' * size
15441550

1545-
with patch.object(hmac, "_compute_digest_fallback") as slow:
1551+
with self.mock__compute_digest_fallback(hmac) as slow:
15461552
hmac.digest(bigkey, b'm', "md5")
15471553
slow.assert_called_once()
15481554

1549-
with patch.object(hmac, "_compute_digest_fallback") as slow:
1555+
with self.mock__compute_digest_fallback(hmac) as slow:
15501556
hmac.digest(b'k', bigmsg, "md5")
15511557
slow.assert_called_once()
15521558

@@ -1557,7 +1563,7 @@ def test_hmac_digest_no_overflow_error_in_fallback(self, size):
15571563

15581564
for key, msg in [(b'K' * size, b'm'), (b'k', b'M' * size)]:
15591565
with self.subTest(keysize=len(key), msgsize=len(msg)):
1560-
with patch.object(hmac, "_compute_digest_fallback") as slow:
1566+
with self.mock__compute_digest_fallback(hmac) as slow:
15611567
hmac.digest(key, msg, "md5")
15621568
slow.assert_called_once()
15631569

Lib/test/test_tarfile.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,37 @@ def test_sparse_file_01(self):
14001400
def test_sparse_file_10(self):
14011401
self._test_sparse_file("gnu/sparse-1.0")
14021402

1403+
def test_sparse_file_10_pax_size(self):
1404+
# gh-83869: when the pax header replaces the size field, the offset
1405+
# of the next header must be computed from the size of the data in
1406+
# the archive, not from the apparent size of the sparse file.
1407+
data = b"payload!" * 4
1408+
realsize = 1 << 20
1409+
smap = b"1\n%d\n%d\n" % (realsize - len(data), len(data))
1410+
smap += b"\0" * (-len(smap) % tarfile.BLOCKSIZE)
1411+
1412+
sparse = tarfile.TarInfo("sparse")
1413+
sparse.size = len(smap) + len(data)
1414+
sparse.pax_headers = {
1415+
"GNU.sparse.major": "1",
1416+
"GNU.sparse.minor": "0",
1417+
"GNU.sparse.name": "sparse",
1418+
"GNU.sparse.realsize": str(realsize),
1419+
"size": str(sparse.size),
1420+
}
1421+
buf = sparse.tobuf(tarfile.PAX_FORMAT)
1422+
buf += smap + data + b"\0" * (-len(data) % tarfile.BLOCKSIZE)
1423+
1424+
last = tarfile.TarInfo("last")
1425+
last.size = len(data)
1426+
buf += last.tobuf(tarfile.PAX_FORMAT)
1427+
buf += data + b"\0" * (-len(data) % tarfile.BLOCKSIZE)
1428+
buf += b"\0" * (tarfile.BLOCKSIZE * 2)
1429+
1430+
with tarfile.open(fileobj=io.BytesIO(buf)) as tar:
1431+
self.assertEqual(tar.getnames(), ["sparse", "last"])
1432+
self.assertEqual(tar.extractfile("last").read(), data)
1433+
14031434
@staticmethod
14041435
def _fs_supports_holes():
14051436
# Return True if the platform knows the st_blocks stat attribute and
@@ -1473,6 +1504,31 @@ def test_pax_global_headers(self):
14731504
finally:
14741505
tar.close()
14751506

1507+
def test_offset_after_global_header(self):
1508+
# gh-83869: a global header is a member of its own, the member which
1509+
# follows it keeps the offset of its own header.
1510+
rec = b"30 comment=global header here\n"
1511+
glob = tarfile.TarInfo("././@PaxHeader")
1512+
glob.type = tarfile.XGLTYPE
1513+
glob.size = len(rec)
1514+
buf = glob.tobuf(tarfile.USTAR_FORMAT)
1515+
buf += rec + b"\0" * (-len(rec) % tarfile.BLOCKSIZE)
1516+
1517+
member = tarfile.TarInfo("member")
1518+
data = b"hello\n"
1519+
member.size = len(data)
1520+
offset = len(buf)
1521+
buf += member.tobuf(tarfile.USTAR_FORMAT)
1522+
buf += data + b"\0" * (-len(data) % tarfile.BLOCKSIZE)
1523+
buf += b"\0" * (tarfile.BLOCKSIZE * 2)
1524+
1525+
with tarfile.open(fileobj=io.BytesIO(buf)) as tar:
1526+
tarinfo = tar.getmember("member")
1527+
self.assertEqual(tarinfo.offset, offset)
1528+
self.assertEqual(tarinfo.pax_headers.get("comment"),
1529+
"global header here")
1530+
self.assertEqual(tar.extractfile(tarinfo).read(), data)
1531+
14761532
def test_pax_number_fields(self):
14771533
# All following number fields are read from the pax header.
14781534
tar = tarfile.open(tarname, encoding="iso8859-1")

0 commit comments

Comments
 (0)