@@ -2,10 +2,12 @@ asyncio
22=======
33
44
5- This document describes the working and implementation details of C
6- implementation of the
5+ This document describes the working and implementation details of the
76[ ` asyncio ` ] ( https://docs.python.org/3/library/asyncio.html ) module.
87
8+ ** The following section describes the implementation details of the C implementation** .
9+
10+ # Task management
911
1012## Pre-Python 3.14 implementation
1113
@@ -158,7 +160,8 @@ flowchart TD
158160 subgraph two["Thread deallocating"]
159161 A1{"thread's task list empty? <br> llist_empty(tstate->asyncio_tasks_head)"}
160162 A1 --> |true| B1["deallocate thread<br>free_threadstate(tstate)"]
161- A1 --> |false| C1["add tasks to interpreter's task list<br> llist_concat(&tstate->interp->asyncio_tasks_head,tstate->asyncio_tasks_head)"]
163+ A1 --> |false| C1["add tasks to interpreter's task list<br> llist_concat(&tstate->interp->asyncio_tasks_head,
164+ &tstate->asyncio_tasks_head)"]
162165 C1 --> B1
163166 end
164167
@@ -205,6 +208,121 @@ In free-threading, it avoids contention on a global dictionary as
205208threads can access the current task of thier running loop without any
206209locking.
207210
211+ ---
212+
213+ ** The following section describes the implementation details of the Python implementation** .
214+
215+ # async generators
216+
217+ This section describes the implementation details of async generators in ` asyncio ` .
218+
219+ Since async generators are meant to be used from coroutines,
220+ their finalization (execution of finally blocks) needs
221+ to be done while the loop is running.
222+ Most async generators are closed automatically
223+ when they are fully iterated over and exhausted; however,
224+ if the async generator is not fully iterated over,
225+ it may not be closed properly, leading to the ` finally ` blocks not being executed.
226+
227+ Consider the following code:
228+ ``` py
229+ import asyncio
230+
231+ async def agen ():
232+ try :
233+ yield 1
234+ finally :
235+ await asyncio.sleep(1 )
236+ print (" finally executed" )
237+
238+
239+ async def main ():
240+ async for i in agen():
241+ break
242+
243+ loop = asyncio.EventLoop()
244+ loop.run_until_complete(main())
245+ ```
246+
247+ The above code will not print "finally executed", because the
248+ async generator ` agen ` is not fully iterated over
249+ and it is not closed manually by awaiting ` agen.aclose() ` .
250+
251+ To solve this, ` asyncio ` uses the ` sys.set_asyncgen_hooks ` function to
252+ set hooks for finalizing async generators as described in
253+ [ PEP 525] ( https://peps.python.org/pep-0525/ ) .
254+
255+ - ** firstiter hook** : When the async generator is iterated over for the first time,
256+ the * firstiter hook* is called. The async generator is added to ` loop._asyncgens ` WeakSet
257+ and the event loop tracks all active async generators.
258+
259+ - ** finalizer hook** : When the async generator is about to be finalized,
260+ the * finalizer hook* is called. The event loop removes the async generator
261+ from ` loop._asyncgens ` WeakSet, and schedules the finalization of the async
262+ generator by creating a task calling ` agen.aclose() ` . This ensures that the
263+ finally block is executed while the event loop is running. When the loop is
264+ shutting down, the loop checks if there are active async generators and if so,
265+ it similarly schedules the finalization of all active async generators by calling
266+ ` agen.aclose() ` on each of them and waits for them to complete before shutting
267+ down the loop.
268+
269+ This ensures that the async generator's ` finally ` blocks are executed even
270+ if the generator is not explicitly closed.
271+
272+ Consider the following example:
273+
274+ ``` python
275+ import asyncio
276+
277+ async def agen ():
278+ try :
279+ yield 1
280+ yield 2
281+ finally :
282+ print (" executing finally block" )
283+
284+ async def main ():
285+ async for item in agen():
286+ print (item)
287+ break # not fully iterated
288+
289+ asyncio.run(main())
290+ ```
291+
292+ ``` mermaid
293+ flowchart TD
294+ subgraph one["Loop running"]
295+ A["asyncio.run(main())"] --> B
296+ B["set async generator hooks <br> sys.set_asyncgen_hooks()"] --> C
297+ C["async for item in agen"] --> F
298+ F{"first iteration?"} --> |true|D
299+ F{"first iteration?"} --> |false|H
300+ D["calls firstiter hook<br>loop._asyncgen_firstiter_hook(agen)"] --> E
301+ E["add agen to WeakSet<br> loop._asyncgens.add(agen)"] --> H
302+ H["item = await agen.\_\_anext\_\_()"] --> J
303+ J{"StopAsyncIteration?"} --> |true|M
304+ J{"StopAsyncIteration?"} --> |false|I
305+ I["print(item)"] --> S
306+ S{"continue iterating?"} --> |true|C
307+ S{"continue iterating?"} --> |false|M
308+ M{"agen is no longer referenced?"} --> |true|N
309+ M{"agen is no longer referenced?"} --> |false|two
310+ N["finalize agen<br>_PyGen_Finalize(agen)"] --> O
311+ O["calls finalizer hook<br>loop._asyncgen_finalizer_hook(agen)"] --> P
312+ P["remove agen from WeakSet<br>loop._asyncgens.discard(agen)"] --> Q
313+ Q["schedule task to close it<br>self.create_task(agen.aclose())"] --> R
314+ R["print('executing finally block')"] --> E1
315+
316+ end
317+
318+ subgraph two["Loop shutting down"]
319+ A1{"check for alive async generators?"} --> |true|B1
320+ B1["close all async generators <br> await asyncio.gather\(*\[ag.aclose\(\) for ag in loop._asyncgens\]"] --> R
321+ A1{"check for alive async generators?"} --> |false|E1
322+ E1["loop.close()"]
323+ end
324+
325+ ```
208326
209327[ ^ 1 ] : https://github.com/python/cpython/issues/123089
210- [ ^ 2 ] : https://github.com/python/cpython/issues/80788
328+ [ ^ 2 ] : https://github.com/python/cpython/issues/80788
0 commit comments