quart icon indicating copy to clipboard operation
quart copied to clipboard

Handling CTRL+C for websockets [linux]

Open tf198 opened this issue 10 months ago • 6 comments

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:

  • Python version: 3.12.2
  • Quart version: 0.19.4

tf198 avatar Apr 10 '24 23:04 tf198

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!

tf198 avatar Apr 11 '24 02:04 tf198

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.

denaillc avatar Apr 25 '24 13:04 denaillc

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

zakx avatar Sep 24 '24 15:09 zakx

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

  • passing my own asyncio.Event as shutdown_trigger when calling hypercorn.asyncio.serve()
  • before calling serve(), setting up a signal handler to trigger that event:
    for sig in (signal.SIGINT, signal.SIGTERM):
        asyncio.get_running_loop().add_signal_handler(sig, aborting.set)
  • in the streaming generator (/ websocket loop, presumably), checking that event and returning if it's set

npt avatar Sep 26 '24 04:09 npt

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.

Erraen avatar Oct 18 '24 11:10 Erraen

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

zakx avatar Oct 18 '24 13:10 zakx