pallets / quart

An async Python micro framework for building web applications.
https://quart.palletsprojects.com
MIT License
3k stars 162 forks source link

Handling CTRL+C for websockets [linux] #333

Open tf198 opened 7 months ago

tf198 commented 7 months ago

Using the minimal example websocket code the quart server hangs on CTRL+C until all clients have closed their connections. This also applies to SSE examples.

app.py

from quart import Quart, websocket
app = Quart(__name__)

@app.websocket('/ws')
async def ws():
    while True:
        data = await websocket.receive()
        await websocket.send(data)

client.py

import websocket, time

ws = websocket.WebSocket()
ws.connect(f'ws://localhost:5000/ws')
i = 0
while True:
    ws.send(str(i))
    data = ws.recv()
    print("GOT", data)
    time.sleep(1)
    i+=1
$ quart run
 * Serving Quart app 'app'
 * Debug mode: False
 * Please use an ASGI server (e.g. Hypercorn) directly in production
 * Running on http://127.0.0.1:5000 (CTRL + C to quit)
[2024-04-11 09:17:11 +1000] [596071] [INFO] Running on http://127.0.0.1:5000 (CTRL + C to quit)
[2024-04-11 09:17:18 +1000] [596071] [INFO] 127.0.0.1:48344 GET /ws 1.1 101 - 2055
^C
**hangs until client disconnects**

The server should be closing all open websockets on SIGINT and allow the errors to propagate for cleanup.

I've been unable to find a nice solution to this. Currently manually adding a SIGINT handler to the current asyncio loop and calling loop.close() but that doesn't allow for nice cleanup.

Environment:

tf198 commented 7 months ago

Managed to get something that shuts down cleanly, including raising asicio.CancelledError on active connections https://github.com/tf198/quart/commit/a6a9ec1e5bdaa4d5e410b4150fa95b5d870af262 Not sure if it the most elegant solution but async messes with my head!

denaillc commented 6 months ago

Also looking for a solution to this in the main branch. When dockerized, my app will not stop running if a socketio conneciton is open until I refresh the client to make the connection go away. Then it terminates.

zakx commented 1 month ago

I have the same issue and have not found a good way to handle this yet.

npt commented 1 month ago

I have the same issue with HTTP streaming as opposed to websockets.

Edit: This seems to really be an issue in Hypercorn — the blocking happens in hypercorn.asyncio.run.worker_serve() when it calls await server.wait_closed(). I managed to handle it, at the cost of some verbosity, by

Erraen commented 3 weeks ago

I have the same problem and it started showing up after switching to python 3.12. After rolling back to 3.11 everything works fine.

zakx commented 3 weeks ago

FWIW, the upstream issue is https://github.com/python/cpython/issues/123720 https://github.com/python/cpython/issues/123720#issuecomment-2400099669