@@ -131,6 +131,17 @@ cdef class Loop:
131131
132132 self ._coroutine_wrapper_set = False
133133
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+
134145 def __init__ (self ):
135146 self .set_debug((not sys_ignore_environment
136147 and bool (os_environ.get(' PYTHONASYNCIODEBUG' ))))
@@ -1081,10 +1092,16 @@ cdef class Loop:
10811092 # This is how asyncio loop behaves.
10821093 mode = uv.UV_RUN_NOWAIT
10831094 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)
10841099 try :
10851100 self ._run(mode)
10861101 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)
10881105
10891106 def close (self ):
10901107 """ Close the event loop.
@@ -2471,6 +2488,50 @@ cdef class Loop:
24712488 await waiter
24722489 return udp, protocol
24732490
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+
24742535
24752536cdef void __loop_alloc_buffer(uv.uv_handle_t* uvhandle,
24762537 size_t suggested_size,
0 commit comments