question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Handle websocket disconnect in event loop

See original GitHub issue

It seems that the starlette.websockets.WebSocketDisconnect exception is not raised when a client disconnects.

This can cause orphaned async tasks as multiple clients connect/disconnect. As someone first referenced on StackOverflow, it can also result in Uvicorn’s shutdown/reload to block.

Example

#!/usr/bin/env python

import asyncio

from starlette.applications import Starlette
from starlette.responses import HTMLResponse

app = Starlette()


@app.route('/')
async def index(req):
    """Return basic websocket connection template at base route"""
    return HTMLResponse('''
        <!DOCTYPE html>
        <html>
            <head>
                <title>WebSocket Example</title>
            </head>
            <body>
                Intentionally empty. Messages echoed to js console.
                <script>
                ws = new WebSocket("ws://localhost:8000/ws");
                ws.onmessage = e => {
                    console.log(e);
                };
                </script>
            </body>
        </html>
        ''')


@app.websocket_route('/ws')
async def ws(websocket):
    await websocket.accept()

    try:
        while True:
            # Expected behavior: print "Looping..." until client
            # disconnects, then close websocket
            print(f'Looping. Client state: {websocket.client_state}')
            # await websocket.send_text('Hello world!')
            await asyncio.sleep(1)
    finally:
        await websocket.close()


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app, log_level='debug')

After accessing the base route to create a websocket connection, closing the window/connection does not raise starlette.websockets.WebSocketDisconnect, leaves the loop running indefinitely, and does not modify websocket.client_state. Additional connections start additional loops.

If you add the line to send the Hello world! message over the websocket after the client has disconnected, websockets.exceptions.ConnectionClosedOK will be raised and can be handled. However, this likely won’t be a workaround for all use cases.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:6
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

8reactions
euri10commented, Dec 18, 2019

you can subclass WebSocketEndpoint @benfasoli , here’s an example of a dummy counter

@app.websocket_route("/counter")
class WebSocketTicks(WebSocketEndpoint):
    async def on_connect(self, websocket: WebSocket) -> None:
        await websocket.accept()
        self.ticker_task = asyncio.create_task(self.tick(websocket))
        logger.debug("connected")

    async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None:
        self.ticker_task.cancel()
        logger.debug("disconnected")

    async def on_receive(self, websocket: WebSocket, data: typing.Any) -> None:
        await websocket.send_json({"Message: ": data})

    async def tick(self, websocket: WebSocket) -> None:
        counter = 0
        while True:
            logger.debug(counter)
            await websocket.send_json({"counter": counter})
            counter += 1
            await asyncio.sleep(1)
1reaction
euri10commented, Feb 14, 2020

understood , the 40 to 50s you see is exactly what is described here: https://github.com/aaugustin/websockets/blob/3bab7fd155636c73b79b258de752b36687bba347/src/websockets/protocol.py#L803-L810

close_timeout default to 10s so maybe setting it to a lower value might help

Read more comments on GitHub >

github_iconTop Results From Across the Web

javascript - Handling connection loss with websockets
I can manually trigger the connection disconnect which works fine, websocket_conn.close();. But if I simply pulled the ethernet cable out the ...
Read more >
WebSocket API: What does it mean that $disconnect is a ...
... WebSocket API provides $connect and $disconnect routes to handle the initial upgrade (handshake) request and the disconnect event.
Read more >
WebSocket: close event - Web APIs - MDN Web Docs
Returns a string indicating the reason the server closed the connection. This is specific to the particular server and sub-protocol. wasClean ...
Read more >
Breaking down Node.js and WebSockets to understand the ...
The event loop is a very simple but important part of how Node.js works. ... This way, Node.js can handle one JavaScript operation...
Read more >
Consumers — Channels 4.0.0 documentation
The two handlers above are handling websocket.connect and websocket.receive ... from inside an AsyncConsumer you're going to hold up the entire event loop, ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found