← index #7471Issue #8717
Related · high · value 1.999
QUERY · ISSUE

Incorrect uasyncio behaviour: tasks are not correctly terminated

openby mattytrentiniopened 2021-06-29updated 2021-07-05
extmod

(First reported on the forum)

The following snippet:

try:
    import uasyncio as asyncio
except ImportError:
    import asyncio

async def count():
    i = 0
    while True:
        print(i)
        i += 1
        await asyncio.sleep(1)

async def main():
    asyncio.create_task(count())
    await asyncio.sleep(5)

asyncio.run(main())
asyncio.run(main())

Produces the following in CPython:

0
1
2
3
4
0
1
2
3
4

But in MicroPython:

0
1
2
3
4
0
5
1
6
2
7
3
8
4
9

It appears that tasks are not correctly terminated (?).

A workaround that produces the correct output is to run the second task in a second event loop:

try:
    import uasyncio as asyncio
except ImportError:
    import asyncio
async def count():
    i = 0
    while True:
        print(i)
        i += 1
        await asyncio.sleep(1)
async def main():
    asyncio.create_task(count())
    await asyncio.sleep(5)
asyncio.run(main())
asyncio.new_event_loop() # Added line
asyncio.run(main())

Note that running either of the snippets repeatedly causes more confusing output because all previous tasks are still running on the queue.

CANDIDATE · ISSUE

There is still a cancellation/race issue in uasyncio wait_for

closedby Kalki70opened 2022-05-31updated 2022-06-17
bugextmod

I know this behavior has been reported before, but I am still seeing it. Tracking a strange condition in our software, where cancelling a task soon after creating it did not work, we found the issue in wait_for and some other reported issues, the last one being #7386

I also found this reported in Python, and a test program mentioned there causes also the issue on micropython : https://bugs.python.org/issue42130

Basically, this code :

import uasyncio as asyncio

async def inner():
    return

async def with_for_coro():
    await asyncio.wait_for(inner(), timeout=100)
    await asyncio.sleep(1)
    print('End of with_for_coro. Should not be reached!')

async def main():
    task = asyncio.create_task(with_for_coro())
    await asyncio.sleep(0)
    assert not task.done()
    task.cancel()
    print('Called task.cancel()')
    await task  # -> You would expect a CancelledError to be raised.

asyncio.run(main())

Running this, no CancelledError is raised when awaiting task in main() and we get this output:

$ micropython cancel.py 
Called task.cancel()
End of with_for_coro. Should not be reached!

Now, if we add a small delay before cancelling, changing for instance to 0.1s:

async def main():
    task = asyncio.create_task(with_for_coro())
    await asyncio.sleep(0.1)

Then we get the expect result :

$ micropython cancel.py 
Called task.cancel()
Traceback (most recent call last):
  File "cancel.py", line 21, in <module>
  File "/usr/lib/micropython/uasyncio/core.py", line 222, in run
  File "/usr/lib/micropython/uasyncio/core.py", line 191, in run_until_complete
  File "/usr/lib/micropython/uasyncio/core.py", line 176, in run_until_complete
  File "cancel.py", line 18, in main
  File "/usr/lib/micropython/uasyncio/task.py", line 145, in __next__
  File "/usr/lib/micropython/uasyncio/core.py", line 183, in run_until_complete
  File "cancel.py", line 9, in with_for_coro
CancelledError: 

This was tested using micropython 1.18

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied