This is not an issue with neither uasyncio nor Cpython asyncio. It's just a general "library problem".
One particluarly frustrating problem is this:
The new uasyncio offers way better task cancellation and there are so many use-cases for task cancellation.
However, cancelling a task can behave like a virus, killing everything connected to that task.
Example:
Code: Select all
try:
import uasyncio as asyncio
except:
import asyncio
async def wait():
print("waiting")
try:
await asyncio.sleep(1)
except asyncio.CancelledError:
print("wait cancelled")
raise
async def awaiter(t):
print("awaiting t")
try:
await t
except asyncio.CancelledError:
print("awaiting cancelled")
async def test_cancel_wait():
aw = asyncio.create_task(wait())
t = []
for _ in range(5):
t.append(asyncio.create_task(awaiter(aw)))
await asyncio.sleep(0.1)
aw.cancel()
await asyncio.sleep(0.1)
async def test_cancel_single_awaiter():
aw = asyncio.create_task(wait())
t = []
for _ in range(5):
t.append(asyncio.create_task(awaiter(aw)))
await asyncio.sleep(0.1)
t[0].cancel()
await asyncio.sleep(0.1)
print("----------------------------\ntest_cancel_wait")
asyncio.run(test_cancel_wait())
print("----------------------------\ntest_cancel_single_awaiter")
asyncio.run(test_cancel_single_awaiter())
In both tests a wait() task "t" is created and 5 "awaiter()" tasks which will wait for "t" to finish.
In the 1. test the task "t" will be cancelled, which therefore propagates the cancellation to all tasks awaiting it. So far absolutely understandable and logical.
In the 2. test one of the "awaiter()" task is cancelled. This now spreads like a virus, killing task "t" and therefore all other "awaiter()" tasks.
The behaviour in Test 2 ("test_cancel_single_awaiter") I absolutely can't understand.. What's even the point of this behaviour? Task "t" with coroutine "wait()" is an independent task, it has no ties to "awaiter()" but still, if an "awaiter()" gets cancelled, the "awaiter()" also cancels "t".
So killing one task awaiting some completely independent task can make your whole program get killed..
Real world example:
Let's make task "t" with coroutine "wait()" a task that subscribes all mqtt topics after a (re)connect.
Also you have 5 "awaiter()" tasks that are going to wait for the subscriptions to be done so they can continue with some other initialization code.
Now let's say one of these "awaiter()" can't wait until everything is susbcribed and has to move on after a short time so it uses wait_for() which behaves the same way as cancelling that task.
So when this wait_for() times out (or the task gets cancelled), our task "t" which subscribes all mqtt topics gets cancelled! (This of course causes all other tasks to receive a CancelledError but that is expected behaviour).
Is there anything I missed in asyncio that would prevent this?
I know I could use an Event, because then cancellations are no problem anymore. But then there's only a limited use in awaiting other Tasks except if you really have only a single task awaiting another task.