Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

RuntimeError: Cannot call "receive" once a disconnect message has been received. #123

@laxuscullen

Description

@laxuscullen

My code:

class RoomChatWebsocket(WebSocketEndpoint):
    encoding = "json"

    @token.required
    async def on_connect(self, websocket: WebSocket):
        await websocket.accept()

        user = websocket.aniko_user
        room_id = websocket.query_params['room_id']
        
        websocket.room_id = room_id

        messages = red.lrange(f"room_chat_{room_id}", 0, -1)

        if not messages:
            red.expire(f"room_chat_{room_id}", red_expiry)

        join_message = {
            "user": {
                "user_id": user.user_id,
                "username": user.username,
                "pfp": user.get_profile_pic(),
                "tier": user.tier,
                "room_id": room_id,
                "is_system": 1
            },
            "message_data": {
                "text": f"@{user.username} has joined the room!",
            }
        }
        red.lpush(f"room_chat_{room_id}", orjson.dumps(join_message).decode('utf-8'))

        messages = red.lrange(f"room_chat_{room_id}", 0, 100)
        data = [orjson.loads(message) for message in messages]
        
        # tell everyone someone joined
        await broadcast.publish(channel=f"room_{room_id}", message=orjson.dumps(join_message).decode('utf-8'))

        # send chat data to newly joined member
        await websocket.send_json(data)

        # handle subscriptions using anyio and broadcaster
        async with anyio.create_task_group() as task_group:
            # Receiver Task
            async def receiver():
                await self.on_message_received(websocket=websocket)
                task_group.cancel_scope.cancel()
            
            task_group.start_soon(receiver)
            
            # Sender Task
            await self.send_message_to_all(websocket)

    async def send_message_to_all(self, websocket):
        async with broadcast.subscribe(channel=f"room_{websocket.room_id}") as subscriber:
            async for event in subscriber:
                await websocket.send_json(orjson.loads(event.message))

    async def on_message_received(self, websocket: WebSocket):
        user = websocket.aniko_user
        room_id = websocket.room_id
        

        try:
            async for data in websocket.iter_json():

                if websocket.client_state != WebSocketState.CONNECTED:
                    return

                chat_data = {
                    "user": {
                        "user_id": user.user_id,
                        "username": user.username,
                        "pfp": user.get_profile_pic(),
                        "tier": user.tier,
                        "room_id": room_id,
                        "is_system": 0
                    },
                    "message_data": {
                        "text": data['text'],
                    }
                }

                chat_data = orjson.dumps(chat_data).decode('utf-8')
                red.lpush(f"room_chat_{room_id}", chat_data)

                # Publish the message to the broadcast channel
                await broadcast.publish(channel=f"room_{room_id}", message=chat_data)
        except WebSocketDisconnect:
            pass
        
    async def on_disconnect(self, websocket: WebSocket, close_code: int):
        user = websocket.aniko_user
        room_id = websocket.room_id
        

        # Notify others that the user has left
        leave_message = {
            "user": {
                "user_id": user.user_id,
                "username": user.username,
                "pfp": user.get_profile_pic(),
                "tier": user.tier,
                "room_id": room_id,
                "is_system": 1
            },
            "message_data": {
                "text": f"@{user.username} has left the room :(",
            }
        }

        leave_message = orjson.dumps(leave_message).decode('utf-8')
        red.lpush(f"room_chat_{room_id}", leave_message)

        # Publish the leave message to the broadcast channel
        await broadcast.publish(channel=f"room_{room_id}", message=leave_message)

        print(f"Disconnected: {websocket}")

Stacktrace:

INFO:     connection closed
Disconnected: <starlette.websockets.WebSocket object at 0x10a907550>
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/venv/lib/python3.11/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 254, in run_asgi
    result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/venv/lib/python3.11/site-packages/starlette/middleware/errors.py", line 149, in __call__
    await self.app(scope, receive, send)
  File "/venv/lib/python3.11/site-packages/starlette/middleware/gzip.py", line 26, in __call__
    await self.app(scope, receive, send)
  File "/venv/lib/python3.11/site-packages/starlette/middleware/cors.py", line 76, in __call__
    await self.app(scope, receive, send)
  File "/venv/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/venv/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/venv/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/venv/lib/python3.11/site-packages/starlette/routing.py", line 341, in handle
    await self.app(scope, receive, send)
  File "/venv/lib/python3.11/site-packages/starlette/endpoints.py", line 89, in dispatch
    raise exc
  File "/venv/lib/python3.11/site-packages/starlette/endpoints.py", line 78, in dispatch
    message = await websocket.receive()
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/venv/lib/python3.11/site-packages/starlette/websockets.py", line 56, in receive
    raise RuntimeError(
RuntimeError: Cannot call "receive" once a disconnect message has been received.

Using:
starlette==0.26.1
uvicorn==0.21.1 & gunicorn==20.1.0 (tested using both)

Error happens when client disconnects. I am testing using insomnia.

I tried try except, checking for client state but to no avail. I hope this project is maintained still because this is the best bet I have in implementing a room chat system.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions