Recommended way of tracking active connections
Hey everyone! Amazing library, been using for a while in production now. :)
I am running into a small issue tracking active websocket connections. I am using a set to keep track of websocket objects, and removing them on ConnectionClosedErrors, however sometimes when the client browser is disconnected, the connection isn't actually closed (or rather, the ConnectionClosedError isn't raised). Are there any best-practices for this? I was thinking of sending a ping to all websockets every 5s, and removing them incase ConnectionClosedError was raised in a separate thread.
class SessionTracker:
def __init__(self):
self.sessions = set()
async def add_session(self, ws):
self.sessions.add(ws)
logger.info(f"Added another session, total sessions: {len(self.sessions)}")
async def drop_session(self, ws):
self.sessions.remove(ws)
logger.info(f"Removed a session, total sessions: {len(self.sessions)}")
async def process_instructions(session_dir, session_id, send, recv):
.... # some processing
async def main():
session_tracker = SessionTracker()
async def ws_session_handler(ws: WebSocketServerProtocol):
async def recv():
return orjson.loads(await ws.recv())
async def send(x):
# logger.info(x)
await ws.send(orjson.dumps(x))
init_data = await recv()
logger.info(
f"Incoming session from {ws.remote_address}. SessionId: {init_data.get('sessionId')}"
)
await session_tracker.add_session(ws)
current_session_id = init_data["sessionId"]
session_dir = str(current_session_id)
session_dir.mkdir(parents=True, exist_ok=True)
try:
await process_instructions(session_dir, current_session_id, send, recv)
except ConnectionClosed as e:
logger.info(f"Connection closed for {current_session_id}")
except Exception as e:
logger.error(traceback.format_exc())
finally:
logger.info(f"Session ended for {current_session_id}")
await session_tracker.drop_session(ws)
await ws.close()
async with serve(ws_session_handler, "0.0.0.0", 8001):
logger.info("Websocket Server Running")
await asyncio.Future()
if __name__ == "__main__":
uvloop.install()
asyncio.run(main())
In some scenarios, a TCP connection can hang and the OS doesn't know or gets stuck trying to close it while the other end no longer responds. In this case, websockets should close the connection after at most 80 seconds with the default settings. (That's ping_interval + ping_timeout + 4 * close_timeout — sorry for the legacy here.) websockets cannot know that the TCP connection is dead if the OS doesn't say so. Could this explain what you are seeing?
Not sure, to be honest. I was able to reproduce it with:
- React renders component -> first websocket connection is established
- React re-renders component -> second websocket connection is established, first websocket object is technically "deleted", but it could be because the connection is re-established and the websocket re-sends "onOpen" while using the the same TCP connection, but the server treats it as two different ones?
Here are my server logs:
2022-09-02 20:21:53.534 | INFO | __main__:ws_session_handler:872 - Incoming session from ('X.X.X.X', 53766).
2022-09-02 20:21:53.534 | INFO | pollock.status_reporting:add_session:34 - Added another session, total sessions: 1
2022-09-02 20:21:53.661 | INFO | pollock.status_reporting:countdown:21 - User connected, cancelling self-destruct.
2022-09-02 20:22:55.835 | INFO | __main__:ws_session_handler:872 - Incoming session from ('X.X.X.X', 46332).
2022-09-02 20:22:55.836 | INFO | pollock.status_reporting:add_session:34 - Added another session, total sessions: 2
^ Session 1 above never gets dropped, even after >2mins.
first websocket object is technically "deleted"
I'm not sure the object is deleted just because you no longer hold a reference. It depends on the garbage collector's behavior.
What if you close() the connection when React unmounts the component? (Or in whichever React lifecycle event happens just before you re-create the connection?)
Closing didn't seem to work, unfortunately. I moved some stuff around so the component actually removes and creates new websocket connection on re-render, but still running into a similar issue. :(
Closing this issue, managed to fix it with improvements to session handling outside of the Websockets library