22Module used to support listening and emitting events.
33It also contains the event loop definitions.
44"""
5- from contextlib import suppress
5+ from contextlib import suppress , asynccontextmanager
66from enum import Enum , auto
77from typing import Any , List , Dict , Callable , TypeVar , Coroutine , Set , Union
8+ from sys import _getframe
89
910from .misc .doc import doc_category
1011
@@ -47,6 +48,7 @@ def __init__(self) -> None:
4748 self .event_queue = asyncio .Queue ()
4849 self .loop_task : asyncio .Task = None
4950 self .running = False
51+ self ._critical_lock = asyncio .Lock ()
5052
5153 def start (self ):
5254 """
@@ -61,6 +63,16 @@ def start(self):
6163 if self is not GLOBAL .g_controller :
6264 GLOBAL .non_global_controllers .add (self )
6365
66+ @asynccontextmanager
67+ async def critical (self ):
68+ """
69+ Asynchronous Context manager (``async with`` statement), that prevents
70+ any events from being processed until this critical section is exited.
71+ """
72+ await self ._critical_lock .acquire ()
73+ yield
74+ self ._critical_lock .release ()
75+
6476 def stop (self ):
6577 "Stops event loop asynchronously"
6678 if self .running :
@@ -142,6 +154,13 @@ def emit(self, event: "EventID", *args, **kwargs) -> asyncio.Future:
142154 future = asyncio .Future ()
143155 if not self .running :
144156 future .set_result (None )
157+ caller_frame = _getframe (1 )
158+ caller_info = caller_frame .f_code
159+ caller_text = f"{ caller_info .co_name } ({ caller_info .co_filename } )"
160+ warnings .warn (
161+ f"{ self } is not running, but { event } was emitted, which was ignored! Caller: { caller_text } " ,
162+ stacklevel = 2
163+ )
145164 return future
146165
147166 self .event_queue .put_nowait ((event , args , kwargs , future ))
@@ -174,21 +193,21 @@ async def event_loop(self):
174193
175194 while self .running :
176195 event_id , args , kwargs , future = await queue .get ()
196+ async with self ._critical_lock :
197+ for listener in listeners .get (event_id , [])[:]:
198+ try :
199+ if listener .predicate is None or listener .predicate (* args , ** kwargs ):
200+ if isinstance (r := listener (* args , ** kwargs ), Coroutine ):
201+ await r
177202
178- for listener in listeners .get (event_id , [])[:]:
179- try :
180- if listener .predicate is None or listener .predicate (* args , ** kwargs ):
181- if isinstance (r := listener (* args , ** kwargs ), Coroutine ):
182- await r
203+ except Exception as exc :
204+ warnings .warn (f"({ exc } ) Could not call event handler { listener } for event { event_id } ." )
205+ future .set_exception (exc )
206+ break
183207
184- except Exception as exc :
185- warnings .warn (f"({ exc } ) Could not call event handler { listener } for event { event_id } ." )
186- future .set_exception (exc )
187- break
188208
189-
190- if not future .done (): # In case exception was set
191- future .set_result (None )
209+ if not future .done (): # In case exception was set
210+ future .set_result (None )
192211
193212 if self is not GLOBAL .g_controller :
194213 GLOBAL .non_global_controllers .remove (self )
@@ -364,6 +383,7 @@ async def server_update(server):
364383 :type server: daf.guild.GUILD | daf.guild.USER | daf.guild.AutoGUILD
365384 """
366385
386+ @doc_category ("Event reference" )
367387async def auto_guild_start_join (auto_guild ):
368388 """
369389 Event that is emitted when the join for new server should start.
0 commit comments