Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Graceful shutdown of uvicorn starlette app with websockets

Given this sample Starlette app with an open websocket connection, how do you shut down the Starlette app? I am running on uvicorn. Whenever I press Ctrl+C the output is Waiting for background tasks to complete. which hangs forever.

from starlette.applications import Starlette

app = Starlette()

@app.websocket_route('/ws')
async def ws(websocket):
    await websocket.accept()

    while True:
        # How to interrupt this while loop on the shutdown event?
        await asyncio.sleep(0.1)

    await websocket.close()

I tried switching a bool variable on the shutdown event but the variable never updates. It is always False.

eg.

app.state.is_shutting_down = False


@app.on_event('shutdown')
async def shutdown():
    app.state.is_shutting_down = True


@app.websocket_route('/ws')
async def ws(websocket):
    await websocket.accept()

    while app.state.is_shutting_down is False:
like image 630
Neil Avatar asked Sep 27 '19 11:09

Neil


Video Answer


1 Answers

The reason your variable didn't change is because the handlers for the "shutdown" event are executed after all the tasks have been executed (i.e. you infinite loop).

Setting a signal handler on the asyncio event loop will probably not work as I believe only a single signal handler is allowed which uvicorn already sets for its own shutdown process.
Instead, you can Monkey Patch the uvicorn signal handler to detect the application shutdown and set your controlling variable in that new function.

import asyncio
from starlette.applications import Starlette
from uvicorn.main import Server

original_handler = Server.handle_exit

class AppStatus:
    should_exit = False

    @staticmethod
    def handle_exit(*args, **kwargs):
        AppStatus.should_exit = True
        original_handler(*args, **kwargs)

Server.handle_exit = AppStatus.handle_exit

app = Starlette()

@app.websocket_route('/ws')
async def ws(websocket):
    await websocket.accept()

    while AppStatus.should_exit is False:

        await asyncio.sleep(0.1)

    await websocket.close()
    print('Exited!')
like image 103
Corky Avatar answered Sep 28 '22 00:09

Corky