micropython-async icon indicating copy to clipboard operation
micropython-async copied to clipboard

Tutorial - gather() - observing different behaviour

Open calebshannon opened this issue 4 years ago • 3 comments

Following your tutorial, you have mentioned that gather can return exceptions with the correct flag set.

However, using the code you provide in section 3.3 gather on MicroPython 1.17, I am entering the asyncio.CancelledError catch block even with the flag set. I believe this is contradictory to the behaviour you described with the return_exceptions=True argument?

>>> %Run -c $EDITOR_CONTENT
Start cancellable bar()
Start barking
Start timeout coro foo()
About to cancel bar
Cancelled
Result:  None

If I simply set bar() to return without being cancelled (code below for completeness sake, but I'm sure you get the idea), I do get the intended behaviour where the timeout error is included in the return collection.

I'm a total microcontroller (and Python) rookie, so I haven't done a whole lot of digging to understand the root cause myself, sorry.

try:
    import uasyncio as asyncio
except ImportError:
    import asyncio

async def barking(n):
    print('Start barking')
    for _ in range(6):
        await asyncio.sleep(1)
    print('Done barking.')
    return 2 * n

async def foo(n):
    print('Start timeout coro foo()')
    while True:
        await asyncio.sleep(1)
        n += 1
    return n

async def bar(n):
    print('Start cancellable bar()')
    return n

async def do_cancel(task):
    await asyncio.sleep(5)
    print('About to cancel bar')
    task.cancel()

async def main():
    tasks = [asyncio.create_task(bar(70))]
    tasks.append(barking(21))
    tasks.append(asyncio.wait_for(foo(10), 7))
    asyncio.create_task(do_cancel(tasks[0]))
    res = None
    try:
        res = await asyncio.gather(*tasks, return_exceptions=True)
    except asyncio.TimeoutError:  # These only happen if return_exceptions is False
        print('Timeout')  # With the default times, cancellation occurs first
    except asyncio.CancelledError:
        print('Cancelled')
    print('Result: ', res)

asyncio.run(main())

>>> %Run -c $EDITOR_CONTENT
Start cancellable bar()
Start barking
Start timeout coro foo()
About to cancel bar
Done barking.
Result:  [70, 42, TimeoutError()]

calebshannon avatar Sep 15 '21 14:09 calebshannon

Well spotted. That is odd, I think a bug must have crept into uasyncio as that script did once work. It still does under CPython. This is the script from the tutorial run under CPython. The outcome is correct:

adminpete@debian8:/mnt/qnap2/temp$ python3
Python 3.8.0 (default, Dec  6 2019, 16:20:13) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rats40
Start cancellable bar()
Start barking
Start timeout coro foo()
About to cancel bar
Done barking.
Result:  [CancelledError(), 42, TimeoutError()]
>>> 

whereas, as you point out, under MicroPython it produces:

>>> import rats40
Start cancellable bar()
Start barking
Start timeout coro foo()
About to cancel bar
Cancelled
Result:  None
>>> 

If I adapt the script so that return_exceptions=False and run it under CPython the outcome is the same as MP:

adminpete@debian8:/mnt/qnap2/temp$ python3
Python 3.8.0 (default, Dec  6 2019, 16:20:13) 
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import rats40
Start cancellable bar()
Start barking
Start timeout coro foo()
About to cancel bar
Cancelled
Result:  None
>>> 

I will try to produce a minimum test case whose behaviour differs between CPython and MicroPython and raise an issue.

peterhinch avatar Sep 16 '21 06:09 peterhinch

I have raised this issue with a simplified script.

peterhinch avatar Sep 16 '21 08:09 peterhinch

Nice! I thought of doing the same but wanted to run it past a pro first.

+1 for actually running example code, eh?

calebshannon avatar Sep 16 '21 10:09 calebshannon

Closing as this issue has been fixed by the maintainers.

peterhinch avatar Nov 25 '22 10:11 peterhinch