I have a very simple FASTAPI app using a websocket route hooked to a busy loop on redis using BRPOP (blocking list pop) from redis.
await ws.accept()
while True:
queue,message = await aioredis_client.brpop(queue_name)
...# some stuff
ws.send_json(response_dict)
Using latest versions of gunicorn, uvicorn, asyncio, uvloop, websockets, fastapi w/ python 3.8.5.
I've tried many many combinations of settings with gunivorn and uvicorn (wsproto vs webosckets, uvloop vs asyncio).
60% of the time it works fine. Then, for no apparent reason messages are sent out vis send_json, but browser never receives them. Restarting gunicorn does nothing, same with switching to uvicorn.
I can fix the issue by blasting the websocket with a ton of messages, then they'll all start flowing again. Help.
I've thrown a sleep in the busy loop, does nothing.
EDIT: I upgraded python to 3.10 and I get a stack now:
File "/home/amalizzio/venv/lib/python3.10/site-packages/starlette/websockets.py", line 163, in send_text
await self.send({"type": "websocket.send", "text": data})
File "/home/amalizzio/venv/lib/python3.10/site-packages/starlette/websockets.py", line 85, in send
await self._send(message)
File "/home/amalizzio/venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in sender
await send(message)
File "/home/amalizzio/venv/lib/python3.10/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 327, in asgi_send
await self.send(data) # type: ignore[arg-type]
File "/home/amalizzio/venv/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 635, in send
await self.ensure_open()
File "/home/amalizzio/venv/lib/python3.10/site-packages/websockets/legacy/protocol.py", line 935, in ensure_open
raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedError: sent 1012 (service restart); no close frame received
EDIT: I switched from websockets to wsproto and I get a similar stack, pasting:
await ws.send_text(j)
File "/home/amalizzio/venv/lib/python3.10/site-packages/starlette/websockets.py", line 163, in send_text
await self.send({"type": "websocket.send", "text": data})
File "/home/amalizzio/venv/lib/python3.10/site-packages/starlette/websockets.py", line 85, in send
await self._send(message)
File "/home/amalizzio/venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in sender
await send(message)
File "/home/amalizzio/venv/lib/python3.10/site-packages/uvicorn/protocols/websockets/wsproto_impl.py", line 322, in send
output = self.conn.send(
File "/home/amalizzio/venv/lib/python3.10/site-packages/wsproto/__init__.py", line 64, in send
data += self.connection.send(event)
File "/home/amalizzio/venv/lib/python3.10/site-packages/wsproto/connection.py", line 107, in send
raise LocalProtocolError(
wsproto.utilities.LocalProtocolError: Event Message(data='...lots of json...', frame_finished=True, message_finished=True) cannot be sent in state ConnectionState.LOCAL_CLOSING.
EDIT I switched from websockets to long polling and I have the same issue I can catch http closing in uvicorn TRACE.
The busy loop on blocking redis is not compatible with starlette or something when i have multiple tabs making connections to the same webserver (plenty of workers). It looks like a thread loses connection to the browser and new threads are spawned, however the list pops and sends the result into the ether! I can see this just be printing before/after popping and magically it's happens 1, then 2, then 3, then 4...
I took off the busy loop and handle all messages straight-through off the pop. I still miss the first message sometimes so I put a blocking wait of 25ms which hasn't broken yet. As a last resort should the queue blocking wait hit the timeout I scan a backup of the queue to see if any messages weren't delivered, so it's definitely reliable now.
I will try to find time to file a bug report. Note: I did switch to async/wait with aioredis and it made absolutely no difference at all.