Skip to content

Commit 1a56843

Browse files
Add a more serious GC check.
1 parent 70b8fc2 commit 1a56843

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

tests/test_core.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import gc
33
import threading
44
import time
5+
import weakref
56
from typing import Any
67

78
import 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+
284332
def 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.

tinyio/_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def cleanup(self):
350350
assert self._waiting_for is not None
351351
assert self._event is not None
352352
self.state = _WaitState.DONE
353-
self._waiting_for = None
353+
self._waiting_for = None # For GC purposes.
354354
del self._event._waits[self]
355355
self._event = None # For GC purposes.
356356

0 commit comments

Comments
 (0)