sim: call .aclose() on TickTrigger in .until() and .repeat()#1590
sim: call .aclose() on TickTrigger in .until() and .repeat()#1590whitequark merged 1 commit intoamaranth-lang:mainfrom
.aclose() on TickTrigger in .until() and .repeat()#1590Conversation
Otherwise, when used together with asyncio, the following warning will
be printed for each use of `.until()`/`.repeat()`:
E: asyncio: Task was destroyed but it is pending!
task: <Task pending name='Task-2' coro=<<async_generator_athrow without __name__>()>>
/usr/lib/python3.13/asyncio/base_events.py:744: RuntimeWarning: coroutine method 'aclose' of 'TickTrigger.__aiter__' was never awaited
f3ac270
|
I am unfortunately running into random failures with this change in my testcases: I'm wondering, why the code needs to be that complex? Can't we simply do async for clk, rst, *values, done in self.sample(condition):
if rst:
raise DomainReset
if done:
return tuple(values)That also fixes the problem for me. The other code segment would probably be written as count = operator.index(count)
if count <= 0:
raise ValueError(f"Repeat count must be a positive integer, not {count!r}")
result = None
async for i, (clk, rst, *values) in enumerate(self):
if i >= count:
break
if rst:
raise DomainReset
assert clk
result = tuple(values)
return resultbut I have not tested that. |
|
Yes, I've seen these failures too. |
|
Would you like to submit your changes as a PR? |
Alright, glad I don't need to build a simplified test case to generate it.
Thinking about it some more, I am not sure if my proposed fix actually calls I tried async with contextlib.aclosing(self.sample(condition).__aiter__()) as tick:
async for clk, rst, *values, done in tick:
if rst:
raise DomainReset
if done:
return tuple(values)but ran into the same issues that the manual handlin in |
|
Truth be told, I don't actually understand the semantics of / need for |
Not to prove something to myself, no, but it might be quite useful to have one for the purpose of understanding this problem better, and also in order to have a regression test. I think asyncio collects all of the async generators you await and then calls |
|
I think I ran in to this issue today, or one close to it. I'm doing something like this small example: import amaranth as am
import amaranth.sim
async def deadline_checker(ctx):
await ctx.tick().repeat(100)
raise RuntimeError('deadline')
m = am.Module()
counter = am.Signal(8)
m.d.sync += counter.eq(counter + 1)
sim = am.sim.Simulator(m)
sim.add_process(deadline_checker)
sim.run()My intention is to run a background process that throws an error after some synchronous deadline, but otherwise is dropped if the rest of the simulation ends. Under Python 3.13.5, this works fine. With Python 3.12.8, I get In general, I get this exception in 3.12 every time a |
Async event loops are responsible for cleaning up async generators. This is a best-effort implementation to call aclose() when possible. Reverts amaranth-lang#1590 and closes amaranth-lang#1638.
Async event loops are responsible for cleaning up async generators. This is a best-effort implementation to call aclose() when possible. Reverts amaranth-lang#1590 and closes amaranth-lang#1638.
Async event loops are responsible for cleaning up async generators. This is a best-effort implementation to call aclose() when possible. Reverts amaranth-lang#1590 and closes amaranth-lang#1638.
Otherwise, when used together with asyncio, the following warning will be printed for each use of
.until()/.repeat():