zauberzeug / nicegui

Create web-based user interfaces with Python. The nice way.
https://nicegui.io
MIT License
10.03k stars 594 forks source link

Graceful shutdown #83

Closed Avriox closed 2 years ago

Avriox commented 2 years ago

Is there any way of gracefully shutting down the server after use? I am using it to have a configuration page for a larger project and once the configuration is completed the main part of the project takes over and no more configuration is needed. I would like to kill everything UI related at that point.

rodja commented 2 years ago

NiceGUI is using uvicorn as a server which has still problems with graceful shutdowns (see https://github.com/encode/uvicorn/discussions/1103 for example). We need to think about this a bit. My not so graceful suggestion would be to start NiceGUI without reload=True, launch a new process on exit and just live with exceptions printed on the console:

from nicegui import ui
import sys

def apply():
    print('Starting main system in seperate process')
    # subprocess.Popen(['python3', 'my_main_system.py'])
    sys.exit(0)

ui.label('Configuration')
ui.label('... your config UI here ...')
ui.button('Apply', on_click=apply)

ui.run(reload=False)
falkoschindler commented 2 years ago

Yes, it would be great to have something like

from nicegui import ui

ui.label('Your configurtion UI...')

def quit():
    ... # How?

ui.button('Quit', on_click=quit)

ui.run(reload=False)

print('Do something else...')

It's a pity that uvicorn.run() starts a server that's not exposed to the outside, but would have a server.shutdown() method.

Monkey-patching uvicorn.run() (or simply copying its content over into our run.py) would be an option. But due to the many configuration options thats more than 120 lines of code and doesn't feel quite right.

rodja commented 2 years ago

The solution multiprocessing.Process-Solution described in https://stackoverflow.com/a/70980373/364388 looked promising. But the whole app needs to be pickled... which does not work in NiceGUI:

AttributeError: Can't pickle local object 'CustomView.use.<locals>.<lambda>'

I've pushed the code to branch #83-Process.

rodja commented 2 years ago

I've got a first idea working on #83-ui.shutdown. The main idea stems from @falkoschindler https://github.com/zauberzeug/nicegui/issues/83#issuecomment-1241787394. We could simply create the uvicorn server object inside nicegui instead of relying on the uvicorn.run helper. The API may look as following:

ui.button('Quit', on_click=ui.shutdown)

But there is still work to be done. For example auto-reloading is still broken.

falkoschindler commented 2 years ago

I tried to add more code from uvicorn.run: https://github.com/zauberzeug/nicegui/commit/501323de11808385571b0c2d90d3a06bd1d8c04d

Now the app starts with reload=False and reload=True. But with auto-reload the "Quit" button does not work, because the spawned process has no nicegui.globals.server set. That's only the case in the parent process. But maybe we don't need reloading anyway. We could simply raise an exception that ui.shutdown does not work with reload=True.

Another issue: When the socket connection is lost due to the server being shut down, the JustPy frontend asks for confirmation to reload the page. This makes no sense and should be avoided.

falkoschindler commented 2 years ago

Nice! After moving ui.shutdown into the lifecycle module the reload-confirmation dialog is gone.

rodja commented 2 years ago

The ui.shutdown() method is now available in v0.8.16

canDry commented 1 year ago

ui.shutdown

Correction: app.shutdown