Skip to content

Commit b313f83

Browse files
authored
Merge branch 'main' into gh-148941-dataclass-init-not-generated-when-user-defined
2 parents 8429874 + 9fd1a12 commit b313f83

45 files changed

Lines changed: 1337 additions & 597 deletions

Some content is hidden

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

Doc/c-api/coro.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ return.
3333
with ``__name__`` and ``__qualname__`` set to *name* and *qualname*.
3434
A reference to *frame* is stolen by this function. The *frame* argument
3535
must not be ``NULL``.
36+
37+
.. deprecated-removed:: 3.16 3.18
38+
39+
This function has not been used since 3.10.
40+
It is also impossible to construct a proper *frame*
41+
object to call this function.

Doc/c-api/gen.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,25 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`.
3838
A reference to *frame* is stolen by this function. The argument must not be
3939
``NULL``.
4040
41+
.. deprecated-removed:: 3.16 3.18
42+
43+
This function has not been used since 3.10.
44+
It is also impossible to construct a proper *frame*
45+
object to call this function.
46+
4147
.. c:function:: PyObject* PyGen_NewWithQualName(PyFrameObject *frame, PyObject *name, PyObject *qualname)
4248
4349
Create and return a new generator object based on the *frame* object,
4450
with ``__name__`` and ``__qualname__`` set to *name* and *qualname*.
4551
A reference to *frame* is stolen by this function. The *frame* argument
4652
must not be ``NULL``.
4753
54+
.. deprecated-removed:: 3.16 3.18
55+
56+
This function has not been used since 3.10.
57+
It is also impossible to construct a proper *frame*
58+
object to call this function.
59+
4860
4961
.. c:function:: PyCodeObject* PyGen_GetCode(PyGenObject *gen)
5062
@@ -77,6 +89,12 @@ Asynchronous Generator Objects
7789
7890
.. versionadded:: 3.6
7991
92+
.. deprecated-removed:: 3.16 3.18
93+
94+
This function has not been used since 3.10.
95+
It is also impossible to construct a proper *frame*
96+
object to call this function.
97+
8098
.. c:function:: int PyAsyncGen_CheckExact(PyObject *op)
8199
82100
Return true if *op* is an asynchronous generator object, false otherwise.

Doc/c-api/memory.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ memory footprint as a whole. Consequently, under certain circumstances, the
7777
Python memory manager may or may not trigger appropriate actions, like garbage
7878
collection, memory compaction or other preventive procedures. Note that by using
7979
the C library allocator as shown in the previous example, the allocated memory
80-
for the I/O buffer escapes completely the Python memory manager.
80+
for the I/O buffer completely escapes the Python memory manager.
8181

8282
.. seealso::
8383

@@ -157,7 +157,7 @@ zero bytes.
157157
158158
.. c:function:: void* PyMem_RawCalloc(size_t nelem, size_t elsize)
159159
160-
Allocates *nelem* elements each whose size in bytes is *elsize* and returns
160+
Allocates *nelem* elements each of size *elsize* bytes and returns
161161
a pointer of type :c:expr:`void*` to the allocated memory, or ``NULL`` if the
162162
request fails. The memory is initialized to zeros.
163163
@@ -235,7 +235,7 @@ In the GIL-enabled build (default build) the
235235
236236
.. c:function:: void* PyMem_Calloc(size_t nelem, size_t elsize)
237237
238-
Allocates *nelem* elements each whose size in bytes is *elsize* and returns
238+
Allocates *nelem* elements each of size *elsize* bytes and returns
239239
a pointer of type :c:expr:`void*` to the allocated memory, or ``NULL`` if the
240240
request fails. The memory is initialized to zeros.
241241
@@ -368,7 +368,7 @@ The :ref:`default object allocator <default-memory-allocators>` uses the
368368
369369
.. c:function:: void* PyObject_Calloc(size_t nelem, size_t elsize)
370370
371-
Allocates *nelem* elements each whose size in bytes is *elsize* and returns
371+
Allocates *nelem* elements each of size *elsize* bytes and returns
372372
a pointer of type :c:expr:`void*` to the allocated memory, or ``NULL`` if the
373373
request fails. The memory is initialized to zeros.
374374

Doc/c-api/module.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,15 @@ Feature slots
247247
If ``Py_mod_multiple_interpreters`` is not specified, the import
248248
machinery defaults to ``Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED``.
249249
250+
For historical reasons, the values are declared as pointers (``void *``).
251+
When using :c:type:`PySlot` arrays, use :c:macro:`PySlot_DATA` for
252+
:c:macro:`!Py_mod_multiple_interpreters`:
253+
254+
.. code-block:: c
255+
256+
PySlot_DATA(Py_mod_multiple_interpreters,
257+
Py_MOD_PER_INTERPRETER_GIL_SUPPORTED)
258+
250259
.. versionadded:: 3.12
251260
252261
.. c:macro:: Py_mod_gil
@@ -272,6 +281,14 @@ Feature slots
272281
If ``Py_mod_gil`` is not specified, the import machinery defaults to
273282
``Py_MOD_GIL_USED``.
274283
284+
For historical reasons, the values are declared as pointers (``void *``).
285+
When using :c:type:`PySlot` arrays, use :c:macro:`PySlot_DATA` for
286+
:c:macro:`!Py_mod_gil`:
287+
288+
.. code-block:: c
289+
290+
PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED)
291+
275292
.. versionadded:: 3.13
276293
277294

Doc/c-api/weakref.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ as much as it can.
4343
should accept a single parameter, which will be the weak reference object
4444
itself. *callback* may also be ``None`` or ``NULL``. If *ob* is not a
4545
weakly referenceable object, or if *callback* is not callable, ``None``, or
46-
``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
46+
``NULL``, this will raise :exc:`TypeError` and return ``NULL``.
47+
48+
.. versionchanged:: next
49+
Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or
50+
``NULL``.
4751
4852
.. seealso::
4953
:c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly
@@ -59,7 +63,11 @@ as much as it can.
5963
collected; it should accept a single parameter, which will be the weak
6064
reference object itself. *callback* may also be ``None`` or ``NULL``. If *ob*
6165
is not a weakly referenceable object, or if *callback* is not callable,
62-
``None``, or ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
66+
``NULL``, this will raise :exc:`TypeError` and return ``NULL``.
67+
68+
.. versionchanged:: next
69+
Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or
70+
``NULL``.
6371
6472
.. seealso::
6573
:c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly

Doc/deprecations/c-api-pending-removal-in-3.18.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Pending removal in Python 3.18
4040
* :c:func:`!_PyUnicodeWriter_PrepareKind`: (no replacement).
4141
* :c:func:`!_Py_HashPointer`: use :c:func:`Py_HashPointer`.
4242
* :c:func:`!_Py_fopen_obj`: use :c:func:`Py_fopen`.
43+
* :c:func:`PyGen_New`: (no replacement).
44+
* :c:func:`PyGen_NewWithQualName`: (no replacement).
45+
* :c:func:`PyCoro_New`: (no replacement).
46+
* :c:func:`PyAsyncGen_New`: (no replacement).
4347

4448
The `pythoncapi-compat project
4549
<https://github.com/python/pythoncapi-compat/>`__ can be used to get

Doc/library/asyncio-threading.rst

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
.. currentmodule:: asyncio
2+
3+
.. _asyncio-threading:
4+
5+
asyncio and free-threaded Python
6+
================================
7+
8+
asyncio uses an event loop as a scheduler to enable highly efficient
9+
concurrency by switching between tasks to allow non-blocking I/O
10+
operations. This results in better performance for I/O-bound use
11+
cases. It also allows off-loading CPU-bound work to a thread or
12+
process pool, but that is still limited by the :term:`global
13+
interpreter lock` in CPython.
14+
15+
However, in :ref:`free-threaded Python <freethreading-python-howto>`,
16+
the GIL is disabled and Python can run true multi-threaded code. This
17+
means that asyncio can now take advantage of multiple CPU cores without
18+
the limitations imposed by the GIL.
19+
20+
Since Python 3.14, asyncio has first-class support for free-threaded
21+
Python, and the implementation of asyncio is safe to use in a
22+
multi-threaded environment.
23+
24+
A single event loop on one core can handle many connections
25+
concurrently, but the Python code that runs to handle each one still
26+
executes serially. Once requests involve a non-trivial amount of
27+
per-request computation, that handling becomes the bottleneck, and a
28+
single core can no longer keep up. Combining asyncio with threads is
29+
most useful here: by running an event loop per thread, the handling of
30+
different requests can run in parallel across multiple CPU cores. It is
31+
also useful when you need to run blocking or CPU-bound code from an
32+
asyncio application.
33+
34+
35+
.. seealso::
36+
37+
`Scaling asyncio on Free-Threaded Python
38+
<https://labs.quansight.org/blog/scaling-asyncio-on-free-threaded-python>`__,
39+
a blog post by Kumar Aditya which explains the internal changes
40+
that make asyncio safe and efficient under free-threaded Python,
41+
together with benchmarks of the resulting improvements.
42+
43+
44+
Thread safety considerations
45+
----------------------------
46+
47+
While asyncio is designed to be thread-safe in a free-threaded Python
48+
environment, there are still some considerations to keep in mind when
49+
using asyncio with threads:
50+
51+
1. **Event loop**: Each thread should have its own event loop which
52+
should not be shared across threads. This ensures that the event loop
53+
can manage its own tasks and callbacks without interference from
54+
other threads.
55+
56+
2. **Task management**: Tasks and futures created in one thread should
57+
not be awaited or manipulated from another thread.
58+
59+
3. **Thread-safe APIs**: When interacting with asyncio from multiple
60+
threads, it's important to use thread-safe APIs provided by asyncio,
61+
such as :func:`asyncio.run_coroutine_threadsafe` for submitting
62+
coroutines to an event loop from another thread. If you need to
63+
call a callback from a different thread, you can use
64+
:meth:`loop.call_soon_threadsafe` to schedule it safely.
65+
66+
4. **Synchronization**: The synchronization primitives provided by
67+
asyncio (like :class:`asyncio.Lock` and :class:`asyncio.Event`)
68+
are not designed to be used across threads. If you need to
69+
synchronize between threads, you should use the synchronization
70+
primitives from the :mod:`threading` module instead.
71+
72+
73+
Using asyncio with threads
74+
--------------------------
75+
76+
asyncio supports running one event loop per thread, which allows you to
77+
take advantage of multiple CPU cores in a free-threaded Python
78+
environment. Each thread can run its own event loop, and tasks can be
79+
scheduled on those loops independently.
80+
81+
Here's an example of how to use asyncio with threads::
82+
83+
import asyncio
84+
import threading
85+
86+
async def worker(name: str) -> None:
87+
print(f"Worker {name} starting")
88+
await asyncio.sleep(1)
89+
print(f"Worker {name} done")
90+
91+
def run_loop(name: str) -> None:
92+
asyncio.run(worker(name))
93+
94+
threads = [
95+
threading.Thread(target=run_loop, args=(f"T{i}",))
96+
for i in range(4)
97+
]
98+
for t in threads:
99+
t.start()
100+
for t in threads:
101+
t.join()
102+
103+
In this example, each thread creates its own event loop with
104+
:func:`asyncio.run` and runs a coroutine on it. The threads execute
105+
concurrently, and in a free-threaded build they can run on separate
106+
CPU cores in parallel.
107+
108+
109+
Producer/consumer across threads
110+
--------------------------------
111+
112+
When a regular (non-asyncio) thread needs to hand work to an asyncio
113+
event loop running in another thread, use a thread-safe primitive such
114+
as :class:`queue.Queue` rather than :class:`asyncio.Queue`, which is
115+
only safe within a single event loop.::
116+
117+
import asyncio
118+
import queue
119+
import threading
120+
121+
def producer(q: queue.Queue[int]) -> None:
122+
for i in range(5):
123+
print(f"Producing {i}")
124+
q.put(i)
125+
q.shutdown()
126+
127+
async def consumer(q: queue.Queue[int]) -> None:
128+
while True:
129+
try:
130+
item = q.get_nowait()
131+
except queue.Empty:
132+
await asyncio.sleep(0.1)
133+
continue
134+
except queue.ShutDown:
135+
break
136+
print(f"Consumed {item}")
137+
await asyncio.sleep(item)
138+
139+
q: queue.Queue[int] = queue.Queue()
140+
consumer_thread = threading.Thread(
141+
target=lambda: asyncio.run(consumer(q))
142+
)
143+
consumer_thread.start()
144+
producer(q)
145+
consumer_thread.join()
146+
147+
The producer runs on the main thread while the consumer runs inside an
148+
event loop on its own thread, yet they communicate safely through
149+
``queue.Queue``. When the queue is empty the consumer sleeps briefly
150+
and tries again. When the producer is done it calls
151+
:meth:`~queue.Queue.shutdown`, which causes subsequent
152+
:meth:`~queue.Queue.get_nowait` calls to raise :exc:`queue.ShutDown`
153+
so the consumer can exit cleanly.
154+

Doc/library/asyncio.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ for full functionality and the latest features.
128128
asyncio-api-index.rst
129129
asyncio-llapi-index.rst
130130
asyncio-dev.rst
131+
asyncio-threading.rst
131132

132133
.. note::
133134
The source code for asyncio can be found in :source:`Lib/asyncio/`.

0 commit comments

Comments
 (0)