@@ -2,10 +2,12 @@ asyncio
2
2
=======
3
3
4
4
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
7
6
[ ` asyncio ` ] ( https://docs.python.org/3/library/asyncio.html ) module.
8
7
8
+ ** The following section describes the implementation details of the C implementation** .
9
+
10
+ # Task management
9
11
10
12
## Pre-Python 3.14 implementation
11
13
@@ -158,7 +160,8 @@ flowchart TD
158
160
subgraph two["Thread deallocating"]
159
161
A1{"thread's task list empty? <br> llist_empty(tstate->asyncio_tasks_head)"}
160
162
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)"]
162
165
C1 --> B1
163
166
end
164
167
@@ -205,6 +208,121 @@ In free-threading, it avoids contention on a global dictionary as
205
208
threads can access the current task of thier running loop without any
206
209
locking.
207
210
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
+ ```
208
326
209
327
[ ^ 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