@@ -131,6 +131,17 @@ cdef class Loop:
131
131
132
132
self ._coroutine_wrapper_set = False
133
133
134
+ if hasattr (sys, ' get_asyncgen_hooks' ):
135
+ # Python >= 3.6
136
+ # A weak set of all asynchronous generators that are
137
+ # being iterated by the loop.
138
+ self ._asyncgens = weakref_WeakSet()
139
+ else :
140
+ self ._asyncgens = None
141
+
142
+ # Set to True when `loop.shutdown_asyncgens` is called.
143
+ self ._asyncgens_shutdown_called = False
144
+
134
145
def __init__ (self ):
135
146
self .set_debug((not sys_ignore_environment
136
147
and bool (os_environ.get(' PYTHONASYNCIODEBUG' ))))
@@ -1081,10 +1092,16 @@ cdef class Loop:
1081
1092
# This is how asyncio loop behaves.
1082
1093
mode = uv.UV_RUN_NOWAIT
1083
1094
self ._set_coroutine_wrapper(self ._debug)
1095
+ if self ._asyncgens is not None :
1096
+ old_agen_hooks = sys.get_asyncgen_hooks()
1097
+ sys.set_asyncgen_hooks(firstiter = self ._asyncgen_firstiter_hook,
1098
+ finalizer = self ._asyncgen_finalizer_hook)
1084
1099
try :
1085
1100
self ._run(mode)
1086
1101
finally :
1087
- self ._set_coroutine_wrapper(0 )
1102
+ self ._set_coroutine_wrapper(False )
1103
+ if self ._asyncgens is not None :
1104
+ sys.set_asyncgen_hooks(* old_agen_hooks)
1088
1105
1089
1106
def close (self ):
1090
1107
""" Close the event loop.
@@ -2471,6 +2488,50 @@ cdef class Loop:
2471
2488
await waiter
2472
2489
return udp, protocol
2473
2490
2491
+ def _asyncgen_finalizer_hook (self , agen ):
2492
+ self ._asyncgens.discard(agen)
2493
+ if not self .is_closed():
2494
+ self .create_task(agen.aclose())
2495
+ # Wake up the loop if the finalizer was called from
2496
+ # a different thread.
2497
+ self ._write_to_self()
2498
+
2499
+ def _asyncgen_firstiter_hook (self , agen ):
2500
+ if self ._asyncgens_shutdown_called:
2501
+ warnings_warn(
2502
+ " asynchronous generator {!r} was scheduled after "
2503
+ " loop.shutdown_asyncgens() call" .format(agen),
2504
+ ResourceWarning, source = self )
2505
+
2506
+ self ._asyncgens.add(agen)
2507
+
2508
+ async def shutdown_asyncgens(self ):
2509
+ """ Shutdown all active asynchronous generators."""
2510
+ self ._asyncgens_shutdown_called = True
2511
+
2512
+ if self ._asyncgens is None or not len (self ._asyncgens):
2513
+ # If Python version is <3.6 or we don't have any asynchronous
2514
+ # generators alive.
2515
+ return
2516
+
2517
+ closing_agens = list (self ._asyncgens)
2518
+ self ._asyncgens.clear()
2519
+
2520
+ shutdown_coro = aio_gather(
2521
+ * [ag.aclose() for ag in closing_agens],
2522
+ return_exceptions = True ,
2523
+ loop = self )
2524
+
2525
+ results = await shutdown_coro
2526
+ for result, agen in zip (results, closing_agens):
2527
+ if isinstance (result, Exception ):
2528
+ self .call_exception_handler({
2529
+ ' message' : ' an error occurred during closing of '
2530
+ ' asynchronous generator {!r}' .format(agen),
2531
+ ' exception' : result,
2532
+ ' asyncgen' : agen
2533
+ })
2534
+
2474
2535
2475
2536
cdef void __loop_alloc_buffer(uv.uv_handle_t* uvhandle,
2476
2537
size_t suggested_size,
0 commit comments