22import gc
33import threading
44import time
5+ import weakref
56from typing import Any
67
78import pytest
@@ -261,7 +262,7 @@ def g():
261262 assert loop .run (g ()) == 3
262263
263264
264- def test_gc ():
265+ def test_gc_simple ():
265266 def _block_add_one (x ):
266267 return x + 1
267268
@@ -281,6 +282,53 @@ def _gc(x: int) -> tinyio.Coro[tuple[int, int]]:
281282 assert set (loop ._results .keys ()) == {coro }
282283
283284
285+ @pytest .mark .parametrize ("yield_from" , (False , True ))
286+ @pytest .mark .parametrize ("timeout" , (None , 10 ))
287+ def test_gc_after_event (yield_from , timeout ):
288+ """The interesting case here is `yield_from=True`, `timeout=10`.
289+ (The others are just for completeness.)
290+
291+ In this case we have that:
292+ - `f1` has `timeout=10` but triggers immediately.
293+ - `f2` has `timeout=2` but triggers at the end of our main coroutine.
294+ And so we have that `f2` is before of `f1` in the internal heap of timeouts, but that `f1` will trigger first.
295+ Thus when `f1` triggers it will remain in that heap even after it has triggered (until `f2` has triggered as well
296+ and they can both be popped).
297+ In this scenario, we don't want the generator object to remain in memory just because it's still sitting in that
298+ heap!
299+ This test checks that the generator can be cleaned up even whilst we wait for the `_Wait` object to get collected
300+ later.
301+ """
302+
303+ def wait (event , wait_time ):
304+ if yield_from :
305+ yield from event .wait (wait_time )
306+ else :
307+ yield event .wait (wait_time )
308+
309+ def set_event (event ):
310+ for _ in range (20 ):
311+ yield
312+ event .set ()
313+
314+ def baz ():
315+ event1 = tinyio .Event ()
316+ event2 = tinyio .Event ()
317+ f1 = wait (event1 , timeout )
318+ f2 = wait (event2 , 2 )
319+ ref = weakref .ref (f1 )
320+ yield {f2 }
321+ yield [f1 , set_event (event1 )]
322+ del f1
323+ gc .collect ()
324+ assert ref () is None
325+ event2 .set ()
326+ return 3
327+
328+ loop = tinyio .Loop ()
329+ assert loop .run (baz ()) == 3
330+
331+
284332def test_event_fairness ():
285333 """This checks that once one event unblocks, that we don't just keep chasing all the stuff downstream of that event,
286334 i.e. that we do schedule work from any other event that has finished.
0 commit comments