quart
quart copied to clipboard
Handling CTRL+C for websockets [linux]
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
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!
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.
I have the same issue and have not found a good way to handle this yet.
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
asshutdown_trigger
when callinghypercorn.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
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.
FWIW, the upstream issue is https://github.com/python/cpython/issues/123720 https://github.com/python/cpython/issues/123720#issuecomment-2400099669