Repository navigation
asyncio.run(...) hangs and neither returns nor raises after asyncio.new_event_loop() called within #19718
Description
Activity
I can confirm this.
However a word of caution may be in order. The usual application for issuing
new_event_loopin asynchronous code is to provide multiple threads with separate event loops. This use of threading is not supported in MicroPython.new_event_loopcreates new task and IO queues, leaving the current task's queues unreferenced and available for GC. Its main use is therefore for cleanup in synchronous code after the termination ofasyncio.run(). Arguablynew_event_loopshould throw if called from asynchronous code.Reacted by Mark A. ZiesemerHowever a word of caution may be in order. The usual application for issuing
new_event_loopin asynchronous code is to provide multiple threads with separate event loops. This is not supported in MicroPython, not least because the task queue is a singleton and is not thread safe.Agreed - which gets into some of your prior writings that I've been separately looking at, including:
- https://github.com/orgs/micropython/discussions/16899
- https://github.com/peterhinch/micropython-async/blob/master/v3/docs/THREADING.md
I do think it would be great to support a 2nd even loop running on the 2nd core (using a 2nd task queue, and proper developer considerations for thread safety, etc.) - but I digress, and another topic for another effort.
I think the primary consideration here stems back to #5890:
Following on from #5820, this PR adds
Loop.new_event_loop()which is used to reset the singleton event loop. This functionality is put here instead of inLoop.close()to make it possible to write code that is compatible with CPython.Here it was decided to use
new_event_loop()not to create an additional event loop, but to "reset the singleton event loop". The above then continues with "to make it possible to write code that is compatible with CPython" - but with what we're seeing here, I do not think this goal was realized, and that this should be revisited?Arguably new_event_loop should throw if called from asynchronous code.
Agreed. At least until such time that maybe it could be used to run a 2nd event loop to run on the 2nd core? Though even then, this should not be called from within asynchronous code.
What to then do with
aioreplin this example, which seems to depend on callingnew_event_loop()to exit (and still really doesn't)? Update it to take in a function that can be called to instead invoke a graceful exit in the app that started it as a task, and then allow itself to simply return and complete its own task? Effectively formalizing what I'm already doing in my current code in patchingasyncio.new_event_loopto accomplish the same?Are there any potential legitimate usages of
new_event_loopalready being called from async that we would need to worry about breaking if this is changed to raise / throw an exception? (Though I can't immediately think of where it would be used legitimately, given what I've already covered above...)
Port, board and/or hardware
RP2 port, RP2350
MicroPython version
MicroPython v1.29.0 on 2026-08-24; Raspberry Pi Pico 2 W with RP2350
Reproduction
mpremote runto execute it on the board.Expected behaviour
Expected to reach and output step # 6, as successfully observed when run with CPython:
Observed behaviour
After a call to
asyncio.new_event_loop, running tasks are aborted, butasyncio.run(...)never returns - neither by exception nor by a graceful exit. The program then hangs indefinitely.(program hangs here)
Additional Information
This is observed when hooking aiorepl into a program, then pressing Ctrl+D during execution, as documented:
As seen at aiorepl.py#L190 in response to that action:
Replacing
task2()above withaiorepl.task()allows for reproducing exactly the same hang, thoughaiorepldoes not directly run under CPython to easily show the comparison.The same can be reproduced by running the
main.pyexample from aiorepl#usage, and uncommenting theprint("task 1")line.taskaborts, but the program never completes. As such, the explanation that one will "return to the regular REPL" does not happen - at least not first without also then using Ctrl+C.I don't believe this is an issue with
aiorepl, but thatasyncio.run(...)should either allow other existing tasks to run to completion and return (as it does in CPython), or at least raise an exception.In all cases, Ctrl+C can and must then be used to raise a KeyboardInterrupt and finally quit the program. However, the issue is that the program itself has no way that I've found to even detect this and either recover or abort.
(For now, as what I think is an ugly workaround, I patch
asyncio.new_event_loopto a method that calls for a graceful stop to my program.)Related Issues
(or at least somewhat related)
asyncio.run()catches KeyboardInterrupt - asyncio.start_server() keeps running #15761Code of Conduct
Yes, I agree