Waitables passed into First can leak Tasks
See original GitHub issueI’ve run into an issue where I had a simulation that slowed substantially as time passed. I eventually traced this to the fact that within the scheduler, there were an excessive number of coroutines waiting on the rising edge of my clock. Further investigation, with the help of @ktbarrett and @marlonjames revealed the issue related to the way I was using First. I was passing a Waitable into First, in this case the ClockCycles trigger. This first was awaited in a while loop and was frequently awaited upon.
while True:
await First(ClockCycles, RisingEdge)
#other logic here
The rising edge trigger frequently fired and the number of clock cycles in the ClockCycles trigger was very large. The issue appears to be with the fact First._wait
calls wait_callback
which in turn makes a call to scheduler._trigger_from_any
. I am told scheduler._trigger_from_any
creates a new task and this task is not cleaned up by First. This is the mechanism by which First leaks tasks.
Let me know if example code is helpful and I’ll be happy to try to create a simple test case.
Environment:
- Cocotb 1.5.2 and 1.6.1
- Alma Linux 8.5
- Riviera Pro 2021.10 64 bit
- Custom Installation of Python 3.7
Issue Analytics
- State:
- Created a year ago
- Comments:6 (6 by maintainers)
Top GitHub Comments
First._wait
creates a new task for each trigger with the_wait_callback
coroutine function (line 884): https://github.com/cocotb/cocotb/blob/b4a2ce033fb21b10e2a2deb0dbb95d0b9e895e9c/cocotb/triggers.py#L873-L884_wait_callback
usesscheduler._trigger_from_any
: https://github.com/cocotb/cocotb/blob/b4a2ce033fb21b10e2a2deb0dbb95d0b9e895e9c/cocotb/triggers.py#L802-L809scheduler._trigger_from_any
callsscheduler._trigger_from_waitable
, which creates another newTask
: https://github.com/cocotb/cocotb/blob/b4a2ce033fb21b10e2a2deb0dbb95d0b9e895e9c/cocotb/scheduler.py#L867-L868When the
RisingEdge
trigger fires,First._wait
kills theTask
waiting in_wait_callback
, but that does not kill the extraTask
created from theClockCycles
waitable.In this situation,
ClockCycles._wait
is a long-lived coroutine, so each time the loop executes, another leakedTask
is created and the old ones are still running.Actually I guess it’s possible to yield
Waitable
s and coroutines and other things from generator-based coroutines. IMO this is something that the@cocotb.coroutine
decorator should handle not the scheduler. It should bridge yielded objects using yield from the object’s__await__
, much likeasyncio.coroutine
does. More ideally, since we are Python 3.6+ now, we can drop the decorator after 1.7.