Skip to content

Commit 70db4a3

Browse files
committed
Catch up with main
2 parents 5cccfa2 + 7b2e01b commit 70db4a3

File tree

81 files changed

+5192
-1353
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+5192
-1353
lines changed

.github/workflows/reusable-tsan.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ jobs:
7474
run: make pythoninfo
7575
- name: Tests
7676
run: ./python -m test --tsan -j4
77+
- name: Parallel tests
78+
if: fromJSON(inputs.free-threading)
79+
run: ./python -m test --tsan-parallel --parallel-threads=4 -j4
7780
- name: Display TSAN logs
7881
if: always()
7982
run: find "${GITHUB_WORKSPACE}" -name 'tsan_log.*' | xargs head -n 1000

.github/workflows/tail-call.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: Tail calling interpreter
2+
on:
3+
pull_request:
4+
paths:
5+
- 'Python/bytecodes.c'
6+
- 'Python/ceval.c'
7+
- 'Python/ceval_macros.h'
8+
push:
9+
paths:
10+
- 'Python/bytecodes.c'
11+
- 'Python/ceval.c'
12+
- 'Python/ceval_macros.h'
13+
workflow_dispatch:
14+
15+
permissions:
16+
contents: read
17+
18+
concurrency:
19+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
20+
cancel-in-progress: true
21+
22+
env:
23+
FORCE_COLOR: 1
24+
25+
jobs:
26+
tail-call:
27+
name: ${{ matrix.target }}
28+
runs-on: ${{ matrix.runner }}
29+
timeout-minutes: 90
30+
strategy:
31+
fail-fast: false
32+
matrix:
33+
target:
34+
# Un-comment as we add support for more platforms for tail-calling interpreters.
35+
# - i686-pc-windows-msvc/msvc
36+
# - x86_64-pc-windows-msvc/msvc
37+
# - aarch64-pc-windows-msvc/msvc
38+
- x86_64-apple-darwin/clang
39+
- aarch64-apple-darwin/clang
40+
- x86_64-unknown-linux-gnu/gcc
41+
- aarch64-unknown-linux-gnu/gcc
42+
llvm:
43+
- 19
44+
include:
45+
# - target: i686-pc-windows-msvc/msvc
46+
# architecture: Win32
47+
# runner: windows-latest
48+
# - target: x86_64-pc-windows-msvc/msvc
49+
# architecture: x64
50+
# runner: windows-latest
51+
# - target: aarch64-pc-windows-msvc/msvc
52+
# architecture: ARM64
53+
# runner: windows-latest
54+
- target: x86_64-apple-darwin/clang
55+
architecture: x86_64
56+
runner: macos-13
57+
- target: aarch64-apple-darwin/clang
58+
architecture: aarch64
59+
runner: macos-14
60+
- target: x86_64-unknown-linux-gnu/gcc
61+
architecture: x86_64
62+
runner: ubuntu-24.04
63+
- target: aarch64-unknown-linux-gnu/gcc
64+
architecture: aarch64
65+
runner: ubuntu-24.04-arm
66+
steps:
67+
- uses: actions/checkout@v4
68+
with:
69+
persist-credentials: false
70+
- uses: actions/setup-python@v5
71+
with:
72+
python-version: '3.11'
73+
74+
- name: Native Windows (debug)
75+
if: runner.os == 'Windows' && matrix.architecture != 'ARM64'
76+
run: |
77+
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0
78+
./PCbuild/build.bat --tail-call-interp -d -p ${{ matrix.architecture }}
79+
./PCbuild/rt.bat -d -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
80+
81+
# No tests (yet):
82+
- name: Emulated Windows (release)
83+
if: runner.os == 'Windows' && matrix.architecture == 'ARM64'
84+
run: |
85+
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0
86+
./PCbuild/build.bat --tail-call-interp -p ${{ matrix.architecture }}
87+
88+
# The `find` line is required as a result of https://github.com/actions/runner-images/issues/9966.
89+
# This is a bug in the macOS runner image where the pre-installed Python is installed in the same
90+
# directory as the Homebrew Python, which causes the build to fail for macos-13. This line removes
91+
# the symlink to the pre-installed Python so that the Homebrew Python is used instead.
92+
- name: Native macOS (debug)
93+
if: runner.os == 'macOS'
94+
run: |
95+
brew update
96+
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
97+
brew install llvm@${{ matrix.llvm }}
98+
export SDKROOT="$(xcrun --show-sdk-path)"
99+
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
100+
export PATH="/usr/local/opt/llvm/bin:$PATH"
101+
CC=clang-19 ./configure --with-tail-call-interp --with-pydebug
102+
make all --jobs 4
103+
./python.exe -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
104+
105+
- name: Native Linux (release)
106+
if: runner.os == 'Linux'
107+
run: |
108+
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
109+
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
110+
CC=clang-19 ./configure --with-tail-call-interp
111+
make all --jobs 4
112+
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
113+

Doc/c-api/init.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1501,7 +1501,7 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
15011501
15021502
.. c:function:: PyObject* PyUnstable_InterpreterState_GetMainModule(PyInterpreterState *interp)
15031503
1504-
Return a :term:`strong reference` to the ``__main__`` `module object <moduleobjects>`_
1504+
Return a :term:`strong reference` to the ``__main__`` :ref:`module object <moduleobjects>`
15051505
for the given interpreter.
15061506
15071507
The caller must hold the GIL.

Doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'changes',
2929
'glossary_search',
3030
'grammar_snippet',
31+
'implementation_detail',
3132
'lexers',
3233
'misc_news',
3334
'pydoc_topics',

Doc/library/imaplib.rst

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
.. changes for IMAP4_SSL by Tino Lange <[email protected]>, March 2002
1111
.. changes for IMAP4_stream by Piers Lauder <[email protected]>,
1212
November 2002
13+
.. changes for IMAP4 IDLE by Forest <[email protected]>, August 2024
1314
1415
**Source code:** :source:`Lib/imaplib.py`
1516

@@ -187,7 +188,7 @@ However, the *password* argument to the ``LOGIN`` command is always quoted. If
187188
you want to avoid having an argument string quoted (eg: the *flags* argument to
188189
``STORE``) then enclose the string in parentheses (eg: ``r'(\Deleted)'``).
189190

190-
Each command returns a tuple: ``(type, [data, ...])`` where *type* is usually
191+
Most commands return a tuple: ``(type, [data, ...])`` where *type* is usually
191192
``'OK'`` or ``'NO'``, and *data* is either the text from the command response,
192193
or mandated results from the command. Each *data* is either a ``bytes``, or a
193194
tuple. If a tuple, then the first part is the header of the response, and the
@@ -307,6 +308,93 @@ An :class:`IMAP4` instance has the following methods:
307308
of the IMAP4 QUOTA extension defined in rfc2087.
308309

309310

311+
.. method:: IMAP4.idle(duration=None)
312+
313+
Return an :class:`!Idler`: an iterable context manager implementing the
314+
IMAP4 ``IDLE`` command as defined in :rfc:`2177`.
315+
316+
The returned object sends the ``IDLE`` command when activated by the
317+
:keyword:`with` statement, produces IMAP untagged responses via the
318+
:term:`iterator` protocol, and sends ``DONE`` upon context exit.
319+
320+
All untagged responses that arrive after sending the ``IDLE`` command
321+
(including any that arrive before the server acknowledges the command) will
322+
be available via iteration. Any leftover responses (those not iterated in
323+
the :keyword:`with` context) can be retrieved in the usual way after
324+
``IDLE`` ends, using :meth:`IMAP4.response`.
325+
326+
Responses are represented as ``(type, [data, ...])`` tuples, as described
327+
in :ref:`IMAP4 Objects <imap4-objects>`.
328+
329+
The *duration* argument sets a maximum duration (in seconds) to keep idling,
330+
after which any ongoing iteration will stop. It can be an :class:`int` or
331+
:class:`float`, or ``None`` for no time limit.
332+
Callers wishing to avoid inactivity timeouts on servers that impose them
333+
should keep this at most 29 minutes (1740 seconds).
334+
Requires a socket connection; *duration* must be ``None`` on
335+
:class:`IMAP4_stream` connections.
336+
337+
.. code-block:: pycon
338+
339+
>>> with M.idle(duration=29 * 60) as idler:
340+
... for typ, data in idler:
341+
... print(typ, data)
342+
...
343+
EXISTS [b'1']
344+
RECENT [b'1']
345+
346+
347+
.. method:: Idler.burst(interval=0.1)
348+
349+
Yield a burst of responses no more than *interval* seconds apart
350+
(expressed as an :class:`int` or :class:`float`).
351+
352+
This :term:`generator` is an alternative to iterating one response at a
353+
time, intended to aid in efficient batch processing. It retrieves the
354+
next response along with any immediately available subsequent responses.
355+
(For example, a rapid series of ``EXPUNGE`` responses after a bulk
356+
delete.)
357+
358+
Requires a socket connection; does not work on :class:`IMAP4_stream`
359+
connections.
360+
361+
.. code-block:: pycon
362+
363+
>>> with M.idle() as idler:
364+
... # get a response and any others following by < 0.1 seconds
365+
... batch = list(idler.burst())
366+
... print(f'processing {len(batch)} responses...')
367+
... print(batch)
368+
...
369+
processing 3 responses...
370+
[('EXPUNGE', [b'2']), ('EXPUNGE', [b'1']), ('RECENT', [b'0'])]
371+
372+
.. tip::
373+
374+
The ``IDLE`` context's maximum duration, as passed to
375+
:meth:`IMAP4.idle`, is respected when waiting for the first response
376+
in a burst. Therefore, an expired :class:`!Idler` will cause this
377+
generator to return immediately without producing anything. Callers
378+
should consider this if using it in a loop.
379+
380+
381+
.. note::
382+
383+
The iterator returned by :meth:`IMAP4.idle` is usable only within a
384+
:keyword:`with` statement. Before or after that context, unsolicited
385+
responses are collected internally whenever a command finishes, and can
386+
be retrieved with :meth:`IMAP4.response`.
387+
388+
.. note::
389+
390+
The :class:`!Idler` class name and structure are internal interfaces,
391+
subject to change. Calling code can rely on its context management,
392+
iteration, and public method to remain stable, but should not subclass,
393+
instantiate, compare, or otherwise directly reference the class.
394+
395+
.. versionadded:: next
396+
397+
310398
.. method:: IMAP4.list([directory[, pattern]])
311399

312400
List mailbox names in *directory* matching *pattern*. *directory* defaults to

Doc/library/itertools.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,10 +838,10 @@ and :term:`generators <generator>` which incur interpreter overhead.
838838

839839
.. testcode::
840840

841-
from collections import deque
841+
from collections import Counter, deque
842842
from contextlib import suppress
843843
from functools import reduce
844-
from math import sumprod, isqrt
844+
from math import comb, prod, sumprod, isqrt
845845
from operator import itemgetter, getitem, mul, neg
846846

847847
def take(n, iterable):
@@ -1127,6 +1127,12 @@ The following recipes have a more mathematical flavor:
11271127
n -= n // prime
11281128
return n
11291129

1130+
def multinomial(*counts):
1131+
"Number of distinct arrangements of a multiset."
1132+
# Counter('abracadabra').values() -> 5 2 1 1 2
1133+
# multinomial(5, 2, 1, 1, 2) → 83160
1134+
return prod(map(comb, accumulate(counts), counts))
1135+
11301136

11311137
.. doctest::
11321138
:hide:
@@ -1730,6 +1736,12 @@ The following recipes have a more mathematical flavor:
17301736
>>> ''.join(it)
17311737
'DEF1'
17321738

1739+
>>> multinomial(5, 2, 1, 1, 2)
1740+
83160
1741+
>>> word = 'coffee'
1742+
>>> multinomial(*Counter(word).values()) == len(set(permutations(word)))
1743+
True
1744+
17331745

17341746
.. testcode::
17351747
:hide:

Doc/library/socket.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,11 +1668,6 @@ to sockets.
16681668
See the Unix manual page :manpage:`recv(2)` for the meaning of the optional argument
16691669
*flags*; it defaults to zero.
16701670

1671-
.. note::
1672-
1673-
For best match with hardware and network realities, the value of *bufsize*
1674-
should be a relatively small power of 2, for example, 4096.
1675-
16761671
.. versionchanged:: 3.5
16771672
If the system call is interrupted and the signal handler does not raise
16781673
an exception, the method now retries the system call instead of raising

Doc/library/socketserver.rst

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -499,11 +499,17 @@ This is the server side::
499499

500500
def handle(self):
501501
# self.request is the TCP socket connected to the client
502-
self.data = self.request.recv(1024).strip()
503-
print("Received from {}:".format(self.client_address[0]))
504-
print(self.data)
502+
pieces = [b'']
503+
total = 0
504+
while b'\n' not in pieces[-1] and total < 10_000:
505+
pieces.append(self.request.recv(2000))
506+
total += len(pieces[-1])
507+
self.data = b''.join(pieces)
508+
print(f"Received from {self.client_address[0]}:")
509+
print(self.data.decode("utf-8"))
505510
# just send back the same data, but upper-cased
506511
self.request.sendall(self.data.upper())
512+
# after we return, the socket will be closed.
507513

508514
if __name__ == "__main__":
509515
HOST, PORT = "localhost", 9999
@@ -520,20 +526,24 @@ objects that simplify communication by providing the standard file interface)::
520526
class MyTCPHandler(socketserver.StreamRequestHandler):
521527

522528
def handle(self):
523-
# self.rfile is a file-like object created by the handler;
524-
# we can now use e.g. readline() instead of raw recv() calls
525-
self.data = self.rfile.readline().strip()
526-
print("{} wrote:".format(self.client_address[0]))
527-
print(self.data)
529+
# self.rfile is a file-like object created by the handler.
530+
# We can now use e.g. readline() instead of raw recv() calls.
531+
# We limit ourselves to 10000 bytes to avoid abuse by the sender.
532+
self.data = self.rfile.readline(10000).rstrip()
533+
print(f"{self.client_address[0]} wrote:")
534+
print(self.data.decode("utf-8"))
528535
# Likewise, self.wfile is a file-like object used to write back
529536
# to the client
530537
self.wfile.write(self.data.upper())
531538

532539
The difference is that the ``readline()`` call in the second handler will call
533540
``recv()`` multiple times until it encounters a newline character, while the
534-
single ``recv()`` call in the first handler will just return what has been
535-
received so far from the client's ``sendall()`` call (typically all of it, but
536-
this is not guaranteed by the TCP protocol).
541+
the first handler had to use a ``recv()`` loop to accumulate data until a
542+
newline itself. If it had just used a single ``recv()`` without the loop it
543+
would just have returned what has been received so far from the client.
544+
TCP is stream based: data arrives in the order it was sent, but there no
545+
correlation between client ``send()`` or ``sendall()`` calls and the number
546+
of ``recv()`` calls on the server required to receive it.
537547

538548

539549
This is the client side::
@@ -548,13 +558,14 @@ This is the client side::
548558
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
549559
# Connect to server and send data
550560
sock.connect((HOST, PORT))
551-
sock.sendall(bytes(data + "\n", "utf-8"))
561+
sock.sendall(bytes(data, "utf-8"))
562+
sock.sendall(b"\n")
552563

553564
# Receive data from the server and shut down
554565
received = str(sock.recv(1024), "utf-8")
555566

556-
print("Sent: {}".format(data))
557-
print("Received: {}".format(received))
567+
print("Sent: ", data)
568+
print("Received:", received)
558569

559570

560571
The output of the example should look something like this:
@@ -599,7 +610,7 @@ This is the server side::
599610
def handle(self):
600611
data = self.request[0].strip()
601612
socket = self.request[1]
602-
print("{} wrote:".format(self.client_address[0]))
613+
print(f"{self.client_address[0]} wrote:")
603614
print(data)
604615
socket.sendto(data.upper(), self.client_address)
605616

@@ -624,8 +635,8 @@ This is the client side::
624635
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
625636
received = str(sock.recv(1024), "utf-8")
626637

627-
print("Sent: {}".format(data))
628-
print("Received: {}".format(received))
638+
print("Sent: ", data)
639+
print("Received:", received)
629640

630641
The output of the example should look exactly like for the TCP server example.
631642

0 commit comments

Comments
 (0)