Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions rendercanvas/_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

import signal
import weakref
from inspect import iscoroutinefunction
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -58,6 +59,7 @@ def __init__(self):
self.__state = (
0 # 0: off, 1: ready, 2: detected-active, 3: inter-active, 4: running
)
self._asyncgens = weakref.WeakSet()
Comment thread
Korijn marked this conversation as resolved.

def __repr__(self):
full_class_name = f"{self.__class__.__module__}.{self.__class__.__name__}"
Expand Down Expand Up @@ -105,6 +107,18 @@ async def _loop_task(self):
# Keep track of event emitter objects
event_emitters = {id(c): c._events for c in self.get_canvases()}

# def init(gen):
# print("init gen", gen)

# def fin(gen):
# print("fin gen", gen)

# print("in loop task", self._using_adapter)
# import sys

# old_agen_hooks = sys.get_asyncgen_hooks()
# sys.set_asyncgen_hooks(init, fin)

try:
while True:
await sleep(0.1)
Expand Down Expand Up @@ -151,6 +165,7 @@ async def _loop_task(self):
del canvas

finally:
# sys.set_asyncgen_hooks(*old_agen_hooks) -> move into __stop
self.__stop()

def add_task(
Expand Down
4 changes: 2 additions & 2 deletions rendercanvas/asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def _rc_run(self):
async def _rc_run_async(self):
import asyncio

# Protect agsinst usage of wrong loop object
# Protect against usage of wrong loop object
libname = sniffio.current_async_library()
if libname != "asyncio":
raise TypeError(f"Attempt to run AsyncioLoop with {libname}.")
Expand All @@ -61,7 +61,7 @@ async def _rc_run_async(self):

# Create tasks if necessay
while self.__pending_tasks:
self._rc_add_task(*self.__pending_tasks.pop(-1))
self._rc_add_task(*self.__pending_tasks.pop(0))

# Wait for loop to finish
if self._stop_event is None:
Expand Down
96 changes: 96 additions & 0 deletions tests/test_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,5 +383,101 @@ def test_async_loops_check_lib():
trio.run(trio_loop.run_async)


async def a_generator(flag):
flag.append("started")
try:
for i in range(10):
await async_sleep(0) # yield back to the loop
yield i
except BaseException as err:
flag.append(f"except {err.__class__.__name__}")
raise
else:
flag.append("finished")
finally:
flag.append("closed")


@pytest.mark.parametrize("SomeLoop", [RawLoop, AsyncioLoop])
def test_async_gens_cleanup0(SomeLoop):
# Don't even start the generator

async def tester_coroutine():
_g = a_generator(flag)

flag = []
loop = SomeLoop()
loop.add_task(tester_coroutine)
loop.call_later(0.1, loop.stop)
loop.run()

assert flag == [], flag


@pytest.mark.parametrize("SomeLoop", [RawLoop, AsyncioLoop])
def test_async_gens_cleanup1(SomeLoop):
# Run the generator to completion

async def tester_coroutine():
g = a_generator(flag)
async for i in g:
pass

flag = []
loop = SomeLoop()
loop.add_task(tester_coroutine)
loop.call_later(0.1, loop.stop)
loop.run()

assert flag == ["started", "finished", "closed"], flag


@pytest.mark.parametrize("SomeLoop", [RawLoop, AsyncioLoop])
def test_async_gens_cleanup2(SomeLoop):
# Break out of the generator, leaving it in a pending state

async def tester_coroutine():
g = a_generator(flag)
# await async_sleep(0)
async for i in g:
if i > 2:
break

flag = []
loop = SomeLoop()
loop.add_task(tester_coroutine)
loop.call_later(0.1, loop.stop)
loop.run()

assert flag == ["started", "except GeneratorExit", "closed"], flag


@pytest.mark.parametrize("SomeLoop", [RawLoop, AsyncioLoop])
def test_async_gens_cleanup3(SomeLoop):
# Break out of the generator, with one extra sleep.
# This made a difference in the outcome at some point.

async def tester_coroutine():
g = a_generator(flag)
await async_sleep(0)
async for i in g:
if i > 2:
break

flag = []
loop = SomeLoop()
loop.add_task(tester_coroutine)
loop.call_later(0.1, loop.stop)
loop.run()

# Ok, this is stupid. This breaks it for asyncio too!!
# I am not able to create a test that
Comment thread
almarklein marked this conversation as resolved.
Outdated

assert flag == ["started", "except GeneratorExit", "closed"], flag
# assert flag == ["started"]


if __name__ == "__main__":
run_tests(globals())
# test_async_gens_cleanup3(AsyncioLoop)
# test_async_gens_cleanup3(RawLoop)