← index #11837Issue #8515
Off-topic · high · value 0.161
QUERY · ISSUE

Zephyr port: async event loop implementation starves CPU

openby bogdanmopened 2023-06-21updated 2023-06-22
enhancementport-zephyr

When using asyncio in MicroPython, the event loop implementation (asyncio.run) ends up polling Python objects in a queue. The polling code does this (extmod/modselect.c:poll_poll_internal):

...
    mp_uint_t start_tick = mp_hal_ticks_ms();
    mp_uint_t n_ready;
    for (;;) {
        // poll the objects
        n_ready = poll_map_poll(&self->poll_map, NULL);
        if (n_ready > 0 || (timeout != (mp_uint_t)-1 && mp_hal_ticks_ms() - start_tick >= timeout)) {
            break;
        }
        MICROPY_EVENT_POLL_HOOK
    }
...

The for(;;) loop above is a busy wait loop. In the Zephyr port, it consumes a lot of CPU time and it starves other threads. Even when MICROPY_EVENT_POOL_HOOK is set to k_yield (and thus the loop gives control back to the OS scheduler after each iteration) the code will still take most of the CPU time. Setting MICROPY_EVENT_POOL_HOOK to something like k_msleep(100) (which waits 100ms before running another iteration of the loop) fixes the starving issue, but it delays the Python thread for no good reason.

I have a possible solution for this, but please let me know first if there is interest to move this forward, since lately I got the impression that the Zephyr port isn't exactly a "fist class citizen" in the world of MicroPython ports.

CANDIDATE · ISSUE

uasyncio : missing RuntimeError: This event loop is already running

openby fragmuffinopened 2022-04-10updated 2026-03-23
extmodport-unix

I've just spent far too long debugging, what is essentially the following code:

#!/usr/bin/env python
try:
    import asyncio
except ImportError:
    import uasyncio as asyncio

loop = asyncio.get_event_loop()

async def foo():
    print('foo()')

async def main():
    loop.run_until_complete(foo())
    while True:
        print('app_loop()')
        await asyncio.sleep(1)

if __name__ == '__main__':
    loop.run_until_complete(main())

I know what you're thinking.... why run_until_complete inside an async function.
I completely agree, and now that I have the root cause, it's easy to fix.

However: this was nested inside a relatively large code-base, and I had no trace-back leading me to the root cause.
Finding this required a lot of cutting code until the problem went away, which wasn't helped by it being in 2 places.

Run on CPython

When run on Python 3.9.9, it raises.

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/usr/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete
    return future.result()
  File "<stdin>", line 2, in main
  File "/usr/lib/python3.9/asyncio/base_events.py", line 623, in run_until_complete
    self._check_running()
  File "/usr/lib/python3.9/asyncio/base_events.py", line 583, in _check_running
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running

Run on micropython

When the above code is executed in micropython I get the following output.

MicroPython v1.17 on 2021-09-02; PYBD-SF6W with STM32F767IIK
Type "help()" for more information.
>>> 
paste mode; Ctrl-C to cancel, Ctrl-D to finish
=== # ... above code pasted here
=== 
foo()
app_loop()
>>> 

Execution took 1 second, no exception raised.

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