Skip to content

Commit 07b58df

Browse files
generatedunixname1734921407115435facebook-github-bot
authored andcommitted
Import upstream CPython branch '3.14'
Summary: Python `3.14.0rc1+` (`3.14`) was **published** on 2025-07-29 06:53:34+00:00. # Commit Info Base: (`3.14.0rc1+`) - `47a2109c1a9dd9fd213d5def2dd60dda12426d37` (commit date: 2025-07-27 06:54:41+00:00) Imported: (`3.14.0rc1+`) - `3.14` (commit date: 2025-07-29 06:53:34+00:00) # Files added ```javascript Misc/NEWS.d/next/Core_and_Builtins/2025-07-24-17-30-58.gh-issue-136870.ncx82J.rst Misc/NEWS.d/next/Library/2025-07-23-00-35-29.gh-issue-130577.c7EITy.rst Misc/NEWS.d/next/Library/2025-07-24-00-38-07.gh-issue-137059.fr64oW.rst ``` Reviewed By: jermenkoo Differential Revision: D79165456 fbshipit-source-id: 5566b9877b8ed0aaa75ba84beaa866b0d5f33fce
1 parent c5891d0 commit 07b58df

11 files changed

Lines changed: 251 additions & 17 deletions

Doc/library/concurrent.futures.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,11 @@ that :class:`ProcessPoolExecutor` will not work in the interactive interpreter.
342342
Calling :class:`Executor` or :class:`Future` methods from a callable submitted
343343
to a :class:`ProcessPoolExecutor` will result in deadlock.
344344

345+
Note that the restrictions on functions and arguments needing to picklable as
346+
per :class:`multiprocessing.Process` apply when using :meth:`~Executor.submit`
347+
and :meth:`~Executor.map` on a :class:`ProcessPoolExecutor`. A function defined
348+
in a REPL or a lambda should not be expected to work.
349+
345350
.. class:: ProcessPoolExecutor(max_workers=None, mp_context=None, initializer=None, initargs=(), max_tasks_per_child=None)
346351

347352
An :class:`Executor` subclass that executes calls asynchronously using a pool

Doc/library/multiprocessing.rst

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ To show the individual process IDs involved, here is an expanded example::
9797
For an explanation of why the ``if __name__ == '__main__'`` part is
9898
necessary, see :ref:`multiprocessing-programming`.
9999

100+
The arguments to :class:`Process` usually need to be unpickleable from within
101+
the child process. If you tried typing the above example directly into a REPL it
102+
could lead to an :exc:`AttributeError` in the child process trying to locate the
103+
*f* function in the ``__main__`` module.
100104

101105

102106
.. _multiprocessing-start-methods:
@@ -233,9 +237,12 @@ processes for a different context. In particular, locks created using
233237
the *fork* context cannot be passed to processes started using the
234238
*spawn* or *forkserver* start methods.
235239

236-
A library which wants to use a particular start method should probably
237-
use :func:`get_context` to avoid interfering with the choice of the
238-
library user.
240+
Libraries using :mod:`multiprocessing` or
241+
:class:`~concurrent.futures.ProcessPoolExecutor` should be designed to allow
242+
their users to provide their own multiprocessing context. Using a specific
243+
context of your own within a library can lead to incompatibilities with the
244+
rest of the library user's application. Always document if your library
245+
requires a specific start method.
239246

240247
.. warning::
241248

@@ -538,9 +545,42 @@ The :mod:`multiprocessing` package mostly replicates the API of the
538545
to pass to *target*.
539546

540547
If a subclass overrides the constructor, it must make sure it invokes the
541-
base class constructor (:meth:`Process.__init__`) before doing anything else
548+
base class constructor (``super().__init__()``) before doing anything else
542549
to the process.
543550

551+
.. note::
552+
553+
In general, all arguments to :class:`Process` must be picklable. This is
554+
frequently observed when trying to create a :class:`Process` or use a
555+
:class:`concurrent.futures.ProcessPoolExecutor` from a REPL with a
556+
locally defined *target* function.
557+
558+
Passing a callable object defined in the current REPL session causes the
559+
child process to die via an uncaught :exc:`AttributeError` exception when
560+
starting as *target* must have been defined within an importable module
561+
in order to be loaded during unpickling.
562+
563+
Example of this uncatchable error from the child::
564+
565+
>>> import multiprocessing as mp
566+
>>> def knigit():
567+
... print("Ni!")
568+
...
569+
>>> process = mp.Process(target=knigit)
570+
>>> process.start()
571+
>>> Traceback (most recent call last):
572+
File ".../multiprocessing/spawn.py", line ..., in spawn_main
573+
File ".../multiprocessing/spawn.py", line ..., in _main
574+
AttributeError: module '__main__' has no attribute 'knigit'
575+
>>> process
576+
<SpawnProcess name='SpawnProcess-1' pid=379473 parent=378707 stopped exitcode=1>
577+
578+
See :ref:`multiprocessing-programming-spawn`. While this restriction is
579+
not true if using the ``"fork"`` start method, as of Python ``3.14`` that
580+
is no longer the default on any platform. See
581+
:ref:`multiprocessing-start-methods`.
582+
See also :gh:`132898`.
583+
544584
.. versionchanged:: 3.3
545585
Added the *daemon* parameter.
546586

@@ -3058,10 +3098,10 @@ start method.
30583098

30593099
More picklability
30603100

3061-
Ensure that all arguments to :meth:`Process.__init__` are picklable.
3062-
Also, if you subclass :class:`~multiprocessing.Process` then make sure that
3063-
instances will be picklable when the :meth:`Process.start
3064-
<multiprocessing.Process.start>` method is called.
3101+
Ensure that all arguments to :class:`~multiprocessing.Process` are
3102+
picklable. Also, if you subclass ``Process.__init__``, you must make sure
3103+
that instances will be picklable when the
3104+
:meth:`Process.start <multiprocessing.Process.start>` method is called.
30653105

30663106
Global variables
30673107

Doc/whatsnew/3.14.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,18 @@ The behavior of :func:`!gc.collect` changes slightly:
10811081

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

1084+
Platform support
1085+
================
1086+
1087+
* :pep:`776`: Emscripten is now an officially supported platform at
1088+
:pep:`tier 3 <11#tier-3>`. As a part of this effort, more than 25 bugs in
1089+
`Emscripten libc`__ were fixed. Emscripten now includes support
1090+
for :mod:`ctypes`, :mod:`termios`, and :mod:`fcntl`, as well as
1091+
experimental support for :ref:`PyREPL <tut-interactive>`.
1092+
1093+
(Contributed by R. Hood Chatham in :gh:`127146`, :gh:`127683`, and :gh:`136931`.)
1094+
1095+
__ https://emscripten.org/docs/porting/emscripten-runtime-environment.html
10841096

10851097
Other language changes
10861098
======================

Lib/tarfile.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,6 +1647,9 @@ def _block(self, count):
16471647
"""Round up a byte count by BLOCKSIZE and return it,
16481648
e.g. _block(834) => 1024.
16491649
"""
1650+
# Only non-negative offsets are allowed
1651+
if count < 0:
1652+
raise InvalidHeaderError("invalid offset")
16501653
blocks, remainder = divmod(count, BLOCKSIZE)
16511654
if remainder:
16521655
blocks += 1

Lib/test/test_tarfile.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def sha256sum(data):
5555
zstname = os.path.join(TEMPDIR, "testtar.tar.zst")
5656
tmpname = os.path.join(TEMPDIR, "tmp.tar")
5757
dotlessname = os.path.join(TEMPDIR, "testtar")
58+
SPACE = b" "
5859

5960
sha256_regtype = (
6061
"e09e4bc8b3c9d9177e77256353b36c159f5f040531bbd4b024a8f9b9196c71ce"
@@ -4602,6 +4603,161 @@ def extractall(self, ar):
46024603
ar.extractall(self.testdir, filter='fully_trusted')
46034604

46044605

4606+
class OffsetValidationTests(unittest.TestCase):
4607+
tarname = tmpname
4608+
invalid_posix_header = (
4609+
# name: 100 bytes
4610+
tarfile.NUL * tarfile.LENGTH_NAME
4611+
# mode, space, null terminator: 8 bytes
4612+
+ b"000755" + SPACE + tarfile.NUL
4613+
# uid, space, null terminator: 8 bytes
4614+
+ b"000001" + SPACE + tarfile.NUL
4615+
# gid, space, null terminator: 8 bytes
4616+
+ b"000001" + SPACE + tarfile.NUL
4617+
# size, space: 12 bytes
4618+
+ b"\xff" * 11 + SPACE
4619+
# mtime, space: 12 bytes
4620+
+ tarfile.NUL * 11 + SPACE
4621+
# chksum: 8 bytes
4622+
+ b"0011407" + tarfile.NUL
4623+
# type: 1 byte
4624+
+ tarfile.REGTYPE
4625+
# linkname: 100 bytes
4626+
+ tarfile.NUL * tarfile.LENGTH_LINK
4627+
# magic: 6 bytes, version: 2 bytes
4628+
+ tarfile.POSIX_MAGIC
4629+
# uname: 32 bytes
4630+
+ tarfile.NUL * 32
4631+
# gname: 32 bytes
4632+
+ tarfile.NUL * 32
4633+
# devmajor, space, null terminator: 8 bytes
4634+
+ tarfile.NUL * 6 + SPACE + tarfile.NUL
4635+
# devminor, space, null terminator: 8 bytes
4636+
+ tarfile.NUL * 6 + SPACE + tarfile.NUL
4637+
# prefix: 155 bytes
4638+
+ tarfile.NUL * tarfile.LENGTH_PREFIX
4639+
# padding: 12 bytes
4640+
+ tarfile.NUL * 12
4641+
)
4642+
invalid_gnu_header = (
4643+
# name: 100 bytes
4644+
tarfile.NUL * tarfile.LENGTH_NAME
4645+
# mode, null terminator: 8 bytes
4646+
+ b"0000755" + tarfile.NUL
4647+
# uid, null terminator: 8 bytes
4648+
+ b"0000001" + tarfile.NUL
4649+
# gid, space, null terminator: 8 bytes
4650+
+ b"0000001" + tarfile.NUL
4651+
# size, space: 12 bytes
4652+
+ b"\xff" * 11 + SPACE
4653+
# mtime, space: 12 bytes
4654+
+ tarfile.NUL * 11 + SPACE
4655+
# chksum: 8 bytes
4656+
+ b"0011327" + tarfile.NUL
4657+
# type: 1 byte
4658+
+ tarfile.REGTYPE
4659+
# linkname: 100 bytes
4660+
+ tarfile.NUL * tarfile.LENGTH_LINK
4661+
# magic: 8 bytes
4662+
+ tarfile.GNU_MAGIC
4663+
# uname: 32 bytes
4664+
+ tarfile.NUL * 32
4665+
# gname: 32 bytes
4666+
+ tarfile.NUL * 32
4667+
# devmajor, null terminator: 8 bytes
4668+
+ tarfile.NUL * 8
4669+
# devminor, null terminator: 8 bytes
4670+
+ tarfile.NUL * 8
4671+
# padding: 167 bytes
4672+
+ tarfile.NUL * 167
4673+
)
4674+
invalid_v7_header = (
4675+
# name: 100 bytes
4676+
tarfile.NUL * tarfile.LENGTH_NAME
4677+
# mode, space, null terminator: 8 bytes
4678+
+ b"000755" + SPACE + tarfile.NUL
4679+
# uid, space, null terminator: 8 bytes
4680+
+ b"000001" + SPACE + tarfile.NUL
4681+
# gid, space, null terminator: 8 bytes
4682+
+ b"000001" + SPACE + tarfile.NUL
4683+
# size, space: 12 bytes
4684+
+ b"\xff" * 11 + SPACE
4685+
# mtime, space: 12 bytes
4686+
+ tarfile.NUL * 11 + SPACE
4687+
# chksum: 8 bytes
4688+
+ b"0010070" + tarfile.NUL
4689+
# type: 1 byte
4690+
+ tarfile.REGTYPE
4691+
# linkname: 100 bytes
4692+
+ tarfile.NUL * tarfile.LENGTH_LINK
4693+
# padding: 255 bytes
4694+
+ tarfile.NUL * 255
4695+
)
4696+
valid_gnu_header = tarfile.TarInfo("filename").tobuf(tarfile.GNU_FORMAT)
4697+
data_block = b"\xff" * tarfile.BLOCKSIZE
4698+
4699+
def _write_buffer(self, buffer):
4700+
with open(self.tarname, "wb") as f:
4701+
f.write(buffer)
4702+
4703+
def _get_members(self, ignore_zeros=None):
4704+
with open(self.tarname, "rb") as f:
4705+
with tarfile.open(
4706+
mode="r", fileobj=f, ignore_zeros=ignore_zeros
4707+
) as tar:
4708+
return tar.getmembers()
4709+
4710+
def _assert_raises_read_error_exception(self):
4711+
with self.assertRaisesRegex(
4712+
tarfile.ReadError, "file could not be opened successfully"
4713+
):
4714+
self._get_members()
4715+
4716+
def test_invalid_offset_header_validations(self):
4717+
for tar_format, invalid_header in (
4718+
("posix", self.invalid_posix_header),
4719+
("gnu", self.invalid_gnu_header),
4720+
("v7", self.invalid_v7_header),
4721+
):
4722+
with self.subTest(format=tar_format):
4723+
self._write_buffer(invalid_header)
4724+
self._assert_raises_read_error_exception()
4725+
4726+
def test_early_stop_at_invalid_offset_header(self):
4727+
buffer = self.valid_gnu_header + self.invalid_gnu_header + self.valid_gnu_header
4728+
self._write_buffer(buffer)
4729+
members = self._get_members()
4730+
self.assertEqual(len(members), 1)
4731+
self.assertEqual(members[0].name, "filename")
4732+
self.assertEqual(members[0].offset, 0)
4733+
4734+
def test_ignore_invalid_archive(self):
4735+
# 3 invalid headers with their respective data
4736+
buffer = (self.invalid_gnu_header + self.data_block) * 3
4737+
self._write_buffer(buffer)
4738+
members = self._get_members(ignore_zeros=True)
4739+
self.assertEqual(len(members), 0)
4740+
4741+
def test_ignore_invalid_offset_headers(self):
4742+
for first_block, second_block, expected_offset in (
4743+
(
4744+
(self.valid_gnu_header),
4745+
(self.invalid_gnu_header + self.data_block),
4746+
0,
4747+
),
4748+
(
4749+
(self.invalid_gnu_header + self.data_block),
4750+
(self.valid_gnu_header),
4751+
1024,
4752+
),
4753+
):
4754+
self._write_buffer(first_block + second_block)
4755+
members = self._get_members(ignore_zeros=True)
4756+
self.assertEqual(len(members), 1)
4757+
self.assertEqual(members[0].name, "filename")
4758+
self.assertEqual(members[0].offset, expected_offset)
4759+
4760+
46054761
def setUpModule():
46064762
os_helper.unlink(TEMPDIR)
46074763
os.makedirs(TEMPDIR)

Lib/test/test_urllib.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,6 +1590,10 @@ def test_url2pathname_resolve_host(self):
15901590
def test_url2pathname_win(self):
15911591
fn = urllib.request.url2pathname
15921592
self.assertEqual(fn('/C:/'), 'C:\\')
1593+
self.assertEqual(fn('//C:'), 'C:')
1594+
self.assertEqual(fn('//C:/'), 'C:\\')
1595+
self.assertEqual(fn('//C:\\'), 'C:\\')
1596+
self.assertEqual(fn('//C:80/'), 'C:80\\')
15931597
self.assertEqual(fn("///C|"), 'C:')
15941598
self.assertEqual(fn("///C:"), 'C:')
15951599
self.assertEqual(fn('///C:/'), 'C:\\')

Lib/urllib/request.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,7 +1660,10 @@ def url2pathname(url, *, require_scheme=False, resolve_host=False):
16601660
if scheme != 'file':
16611661
raise URLError("URL is missing a 'file:' scheme")
16621662
if os.name == 'nt':
1663-
if not _is_local_authority(authority, resolve_host):
1663+
if authority[1:2] == ':':
1664+
# e.g. file://c:/file.txt
1665+
url = authority + url
1666+
elif not _is_local_authority(authority, resolve_host):
16641667
# e.g. file://server/share/file.txt
16651668
url = '//' + authority + url
16661669
elif url[:3] == '///':
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix data races while de-instrumenting bytecode of code objects running concurrently in threads.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`tarfile` now validates archives to ensure member offsets are
2+
non-negative. (Contributed by Alexander Enrique Urieles Nieto in
3+
:gh:`130577`.)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix handling of file URLs with a Windows drive letter in the URL authority
2+
by :func:`urllib.request.url2pathname`. This fixes a regression in earlier
3+
pre-releases of Python 3.14.

0 commit comments

Comments
 (0)