Skip to content

asyncio not using ProactorEventLoop with reload enabled #3874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
lausfl opened this issue Oct 15, 2024 · 12 comments
Closed

asyncio not using ProactorEventLoop with reload enabled #3874

lausfl opened this issue Oct 15, 2024 · 12 comments
Labels
🌱 beginner Difficulty: Good for first-time contributors bug Type/scope: A problem that needs fixing ⚪️ minor Priority: Low impact, nice-to-have question Status: Needs clarification from the author

Comments

@lausfl
Copy link

lausfl commented Oct 15, 2024

Description

Version information:

  • OS: Windows 10.0.19045
  • Python: 3.11.4
  • NiceGUI: 2.3.0

I was unable to find any documentation or existing issues about this and I am not sure if I am doing something wrong here.

I want to create an application that uses NiceGUI and has a separate process running from which I can parse the output on stdout and do something with it.
The problem I encounter is that I am unable to start the process when the reload argument of ui.run() is set to True (a NotImplementedError exception is thrown by asyncio).
On further investigation I found that the asyncio event loop is set to _WindowsSelectorEventLoop when reload is enabled and to ProactorEventLoop when it's disabled.

I found the following issue, which seems to be related: encode/uvicorn#1220

So I tried setting the event loop policy to try and force it to use a ProactorEventLoop, but to no avail.

Here's the MRE:

import asyncio

asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
from nicegui import ui, app

def on_packet(packet):
    print('packet received')

async def start_ping():
    print(type(asyncio.get_event_loop()).__name__)
    cmd = ['ping', '127.0.0.1']
    proc = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE)
    async for line in proc.stdout:
        print(line)

ui.label('Hello NiceGUI!')

app.on_startup(start_ping)
ui.run(reload=True)

When I change the above code to ui.run(reload=False), it works as intended.

@lausfl lausfl changed the title asyncio Event Loop Policy not Respected asyncio not using ProactorEventLoop with reload enabled Oct 15, 2024
@rodja
Copy link
Member

rodja commented Oct 16, 2024

Have you tried using a main guard? The subprocess evaluates the main file again, but should not start another NiceGUI application.

@lausfl
Copy link
Author

lausfl commented Oct 16, 2024

@rodja do you mean like this?

import asyncio

asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
from nicegui import ui, app

def on_packet(packet):
    print('packet received')

async def start_ping():
    print(type(asyncio.get_event_loop()).__name__)
    cmd = ['ping', '127.0.0.1']
    proc = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE)
    async for line in proc.stdout:
        print(line)

if __name__ == '__mp_main__':
    ui.label('Hello NiceGUI!')

app.on_startup(start_ping)
ui.run(reload=True)

I also tried putting app.on_startup(start_ping) inside the guard. Either way I get the same behaviour.

@rodja
Copy link
Member

rodja commented Oct 17, 2024

The ui.run() must be guarded:

...
if __name__ in ('__mp_main__', '__main__'):
    ui.run()

@lausfl
Copy link
Author

lausfl commented Oct 18, 2024

It's still using the _WindowsSelectorEventLoop with the guard. I also tried putting all three lines inside the guard, but nothing changes.
Is this only happening on my end or can you reproduce it?

import asyncio

asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
from nicegui import ui, app

def on_packet(packet):
    print('packet received')

async def start_ping():
    print(type(asyncio.get_event_loop()).__name__)
    cmd = ['ping', '127.0.0.1']
    proc = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE)
    async for line in proc.stdout:
        print(line)

ui.label('Hello NiceGUI!')
app.on_startup(start_ping)

if __name__ in ('__mp_main__', '__main__'):
    ui.run()

@rodja
Copy link
Member

rodja commented Oct 18, 2024

We do not use Windows. Maybe someone from the community?

@falkoschindler
Copy link
Contributor

@lausfl Is the problem still persisting? Can anyone else reproduce it?

@falkoschindler falkoschindler added bug Type/scope: A problem that needs fixing question Status: Needs clarification from the author 🌱 beginner Difficulty: Good for first-time contributors ⚪️ minor Priority: Low impact, nice-to-have labels Mar 28, 2025
@evnchn
Copy link
Collaborator

evnchn commented Mar 28, 2025

It is basically a matter-of-fact that when uvicorn is run with reload=True (yes, I should remind you that the hot-reload functionality comes from uvicorn, not us), that reload=True ==> SelectorEventLoop, reload=False ==> ProactorEventLoop.

Force the loop policy to SelectorEventLoop on Windows with 3.8+
encode/uvicorn#529 (comment)
https://github.com/encode/uvicorn/pull/535/files


So, you can either:

  • Mess with Uvicorn to un-force the loop policy to SelectorEventLoop
  • Accept the fact and use reload=False

Also: I can't seem to be able to use WinLoop for this. I am still investigating why.

@evnchn
Copy link
Collaborator

evnchn commented Mar 28, 2025

@lausfl Check this out. WinLoop to the rescue, and now we force uvicorn to not set the event loop in any way by passing loop="none".

Image

Source code:

import asyncio
from nicegui import ui, app
from fastapi import FastAPI
import winloop

fapp = FastAPI()

@ui.page("/")
def main_page():
    ui.label(str(type(asyncio.get_event_loop())))

async def start_ping():
    print(str(type(asyncio.get_event_loop())))
    cmd = ['ping', '127.0.0.1']
    proc = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE)
    async for line in proc.stdout:
        print(line)

app.on_startup(start_ping)

asyncio.set_event_loop(winloop.new_event_loop())
asyncio.set_event_loop_policy(winloop.EventLoopPolicy())

ui.run(loop="none", port=9000) # let us configure the loop ourselves

Regarding #4474: I may have to update my code snippet to fully use WinLoop. Apparently just doing winloop.install() (which is asyncio.set_event_loop_policy(winloop.EventLoopPolicy()) in disguise) may not be enough in fully setting everything to use WinLoop.

@lausfl
Copy link
Author

lausfl commented Mar 31, 2025

@falkoschindler yes, I just tested it with the latest version of nicegui and it still occurs.

@evnchn
Copy link
Collaborator

evnchn commented Mar 31, 2025

Also test the winloop implementation?

@lausfl
Copy link
Author

lausfl commented Mar 31, 2025

@evnchn thanks a lot for the alternative solution. I am closing the issue, as my problem can be solved by using WinLoop.

@lausfl lausfl closed this as completed Mar 31, 2025
@lausfl
Copy link
Author

lausfl commented Mar 31, 2025

@evnchn WinLoop works as expected, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🌱 beginner Difficulty: Good for first-time contributors bug Type/scope: A problem that needs fixing ⚪️ minor Priority: Low impact, nice-to-have question Status: Needs clarification from the author
Projects
None yet
Development

No branches or pull requests

4 participants