I’m trying to cancel a task that is awaiting on the completion of another future, however this cause the code to abort.
Looking a bit into the code, it seems that
AsyncTaskChain::do_remove() (which is called by
cancel()) does not remove a task in
cancel()method checks if the task is done after calling remove, and, if not, an assert is triggered.
from panda3d.core import AsyncFuture
future = AsyncFuture()
async def waiting_task():
async def cancel_task(t):
t = taskMgr.add(waiting_task())
And the output is :
Assertion failed: done() at line 465 of panda/src/event/asyncTask.cxx
:task(error): PythonTask cancel_task exception was never retrieved:
Traceback (most recent call last):
File "test/test_async3.py", line 10, in cancel_task
AssertionError: done() at line 465 of panda/src/event/asyncTask.cxx
This is in fact implemented, but only on the master branch, not in 1.10. In 1.10,
cancel() is an alias for
remove(). On the master branch, it reschedules the task and throws a CancelledException into it.
Thanks for the info! Perhaps it´s time for me to switch to the master branch.
I want to add that I’m not sure whether we yet implemented the behaviour of asyncio, which is (I believe) that the future you’re awaiting inside the cancelled task also gets cancelled (eg. if it’s a cancellable request like a
tex.prepare() call, an interval, or a
ModelLoadRequest). I actually feel a little ambivalent about whether that should be the right behaviour, but we should probably match what asyncio does.
With asyncio, if you’re awaiting on a future in a task and the task gets cancelled the future is also cancelled (I believe using a call to the
cancel() method if I understand correctly
Thanks for confirming. Could you please file a feature request for 1.11 that futures being awaited by a coroutine task should be cancelled if the task is cancelled?
I modified my code to use the new feature, it works fine (thank you for your great work btw !) except with
If I cancel a task awaiting on a
gather(), the code aborts with either :
AssertionError: !AtomicAdjust::dec(_num_pending) at line 422 of panda/src/event/asyncFuture.cxx
AssertionError: unexpected task state at line 353 of panda/src/event/asyncFuture.cxx
Here is a piece of code that trigger the issue :
from direct.task.Task import gather
from panda3d.core import AsyncFuture
f1 = AsyncFuture()
f2 = AsyncFuture()
async def t1():
async def t2():
async def test():
await gather(t1(), t2())
t = taskMgr.add(test(), 'test')
taskMgr.add(cancel, 'cancel', extraArgs=[t])
Note that with this code I can only get the
!AtomicAdjust::dec(_num_pending) assertion error.
It’s because the
AsyncGatheringFuture::cancel() code assumes that if
cancel() on a future returns true, it is really cancelled right then and there; but this is no longer true because calling
cancel() on a task only schedules it to be cancelled in the future (and then it may still resist the cancellation).
The solution is to change
gather().cancel() to make fewer assumptions.
As for “unexpected task state”, this is a rare corner case: cancelling the task is also causing the
gather() to be cancelled, which would be fine if it were taking effect right away, but this happens delayed since it needs to wait for the contained task to (possibly) handle the cancellation. So, the task gets reactivated but then when the
gather() finishes being cancelled it tries to also reactivate the task, and gets confused because it is already active. I think I can just handle this with a no-op if the task is already active.
Checked in fixes for both issues to the master branch.
Thanks for the fixes and the explanation ! I made a new build with these fixes and so far I don’t get any errors any more. I will continue stressing the app to see if everything is stable.
Bw, I’m using the coroutine tasks inside a common thread but also across threads and it works as expected for both cases