@@ -102,3 +102,106 @@ def on_error_another() -> None:
102102 emitter .emit ("error" )
103103
104104 assert len (calls ) == 2
105+
106+
107+ class _OrderedHandler :
108+ """A callable whose hash is fixed, so a set orders it independently of registration."""
109+
110+ def __init__ (self , name : str , hash_value : int , sink : list [str ]) -> None :
111+ self ._name = name
112+ self ._hash = hash_value
113+ self ._sink = sink
114+
115+ def __hash__ (self ) -> int :
116+ return self ._hash
117+
118+ def __eq__ (self , other : object ) -> bool :
119+ return self is other
120+
121+ def __call__ (self ) -> None :
122+ self ._sink .append (self ._name )
123+
124+
125+ def test_handlers_run_in_registration_order () -> None :
126+ # Handlers were kept in a set, so dispatch order was hash-derived. The hashes here are
127+ # picked so a set yields them in the opposite order to the one they were added in.
128+ emitter = EventEmitter [str ]()
129+ order : list [str ] = []
130+
131+ emitter .on ("event" , _OrderedHandler ("first" , 5 , order ))
132+ emitter .on ("event" , _OrderedHandler ("second" , 1 , order ))
133+
134+ emitter .emit ("event" )
135+ assert order == ["first" , "second" ]
136+
137+
138+ def test_a_mutating_handler_runs_before_a_peer_that_reads_it () -> None :
139+ # The livekit-agents case: one handler stamps a field onto the emitted object and a
140+ # user handler registered later reads it. Registration order has to decide.
141+ class Event :
142+ def __init__ (self ) -> None :
143+ self .speech_id : Any = None
144+
145+ class Stamp :
146+ def __hash__ (self ) -> int :
147+ return 5
148+
149+ def __eq__ (self , other : object ) -> bool :
150+ return self is other
151+
152+ def __call__ (self , ev : Event ) -> None :
153+ ev .speech_id = "speech_1"
154+
155+ class Read :
156+ def __init__ (self , sink : list [Any ]) -> None :
157+ self ._sink = sink
158+
159+ def __hash__ (self ) -> int :
160+ return 1
161+
162+ def __eq__ (self , other : object ) -> bool :
163+ return self is other
164+
165+ def __call__ (self , ev : Event ) -> None :
166+ self ._sink .append (ev .speech_id )
167+
168+ emitter = EventEmitter [str ]()
169+ seen : list [Any ] = []
170+ emitter .on ("metrics" , Stamp ())
171+ emitter .on ("metrics" , Read (seen ))
172+
173+ for _ in range (5 ):
174+ emitter .emit ("metrics" , Event ())
175+
176+ assert seen == ["speech_1" ] * 5
177+
178+
179+ def test_off_still_removes_a_handler () -> None :
180+ emitter = EventEmitter [str ]()
181+ calls : list [str ] = []
182+
183+ @emitter .on ("event" )
184+ def keep () -> None :
185+ calls .append ("keep" )
186+
187+ @emitter .on ("event" )
188+ def drop () -> None :
189+ calls .append ("drop" )
190+
191+ emitter .off ("event" , drop )
192+ emitter .off ("event" , drop ) # removing twice must not raise
193+ emitter .emit ("event" )
194+ assert calls == ["keep" ]
195+
196+
197+ def test_registering_the_same_handler_twice_keeps_one_entry () -> None :
198+ emitter = EventEmitter [str ]()
199+ calls : list [str ] = []
200+
201+ def handler () -> None :
202+ calls .append ("x" )
203+
204+ emitter .on ("event" , handler )
205+ emitter .on ("event" , handler )
206+ emitter .emit ("event" )
207+ assert calls == ["x" ]
0 commit comments