From bdc8f149dc1cffe4ae097669afeb7971d81c67c9 Mon Sep 17 00:00:00 2001 From: retoor Date: Wed, 17 Dec 2025 22:47:21 +0100 Subject: [PATCH] refactor: remove presence debounce for instant user departures refactor: simplify websocket connection and error handling in rpc view --- CHANGELOG.md | 8 +++++ pyproject.toml | 2 +- src/snek/service/socket.py | 28 +-------------- src/snek/view/rpc.py | 70 ++++++++++++++++++-------------------- 4 files changed, 43 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 017b077..0d5eba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ + +## Version 1.6.0 - 2025-12-17 + +Removes presence debounce to make user departures instant. Simplifies websocket connection and error handling in RPC views for improved reliability. + +**Changes:** 2 files, 98 lines +**Languages:** Python (98 lines) + ## Version 1.5.0 - 2025-12-17 remove umami analytics script diff --git a/pyproject.toml b/pyproject.toml index e1f85b1..4d42d96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "Snek" -version = "1.5.0" +version = "1.6.0" readme = "README.md" #license = { file = "LICENSE", content-type="text/markdown" } description = "Snek Chat Application by Molodetz" diff --git a/src/snek/service/socket.py b/src/snek/service/socket.py index e11aae0..f1a3eaf 100644 --- a/src/snek/service/socket.py +++ b/src/snek/service/socket.py @@ -8,9 +8,6 @@ from snek.system.service import BaseService logger = logging.getLogger(__name__) from snek.system.model import now -PRESENCE_DEBOUNCE_SECONDS = 3.0 - - class SocketService(BaseService): class Socket: @@ -48,7 +45,6 @@ class SocketService(BaseService): self.users = {} self.subscriptions = {} self.last_update = str(datetime.now()) - self._departure_tasks = {} async def user_availability_service(self): logger.info("User availability update service started.") @@ -90,10 +86,6 @@ class SocketService(BaseService): logger.info(f"Added socket for user {s.user['username']}") is_first_connection = False - if user_uid in self._departure_tasks: - self._departure_tasks[user_uid].cancel() - del self._departure_tasks[user_uid] - if not self.users.get(user_uid): self.users[user_uid] = set() is_first_connection = True @@ -147,25 +139,7 @@ class SocketService(BaseService): if user_uid in self.users: self.users[user_uid].discard(s) if len(self.users[user_uid]) == 0: - await self._schedule_departure(user_uid, user_nick, user_color) - - async def _schedule_departure(self, user_uid, user_nick, user_color): - if user_uid in self._departure_tasks: - return - - async def delayed_departure(): - try: - await asyncio.sleep(PRESENCE_DEBOUNCE_SECONDS) - if user_uid in self.users and len(self.users[user_uid]) == 0: - await self._broadcast_presence("departed", user_uid, user_nick, user_color) - if user_uid in self._departure_tasks: - del self._departure_tasks[user_uid] - except asyncio.CancelledError: - pass - except Exception as ex: - logger.warning(f"Error in departure broadcast: {ex}") - - self._departure_tasks[user_uid] = asyncio.create_task(delayed_departure()) + await self._broadcast_presence("departed", user_uid, user_nick, user_color) async def _broadcast_presence(self, event_type, user_uid, user_nick, user_color): if not user_uid or not user_nick: diff --git a/src/snek/view/rpc.py b/src/snek/view/rpc.py index fe6bcba..67892c5 100644 --- a/src/snek/view/rpc.py +++ b/src/snek/view/rpc.py @@ -627,44 +627,40 @@ class RPCView(BaseView): ws = web.WebSocketResponse() await ws.prepare(self.request) - user_uid = self.request.session.get("uid") if self.request.session.get("logged_in") else None - - try: - if user_uid: - await self.services.socket.add(ws, user_uid) - async for subscription in self.services.channel_member.find( - user_uid=user_uid, - deleted_at=None, - is_banned=False, - ): - await self.services.socket.subscribe( - ws, subscription["channel_uid"], user_uid - ) - if not scheduled and self.request.app.uptime_seconds < 5: - await schedule( - user_uid, - 0, - {"event": "refresh", "data": {"message": "Finishing deployment"}}, - ) - await schedule( - user_uid, - 15, - {"event": "deployed", "data": {"uptime": self.request.app.uptime}}, + if self.request.session.get("logged_in"): + await self.services.socket.add(ws, self.request.session.get("uid")) + async for subscription in self.services.channel_member.find( + user_uid=self.request.session.get("uid"), + deleted_at=None, + is_banned=False, + ): + await self.services.socket.subscribe( + ws, subscription["channel_uid"], self.request.session.get("uid") ) + if not scheduled and self.request.app.uptime_seconds < 5: + await schedule( + self.request.session.get("uid"), + 0, + {"event": "refresh", "data": {"message": "Finishing deployment"}}, + ) + await schedule( + self.request.session.get("uid"), + 15, + {"event": "deployed", "data": {"uptime": self.request.app.uptime}}, + ) - rpc = RPCView.RPCApi(self, ws) - async for msg in ws: - if msg.type == web.WSMsgType.TEXT: - try: - await rpc(msg.json()) - except Exception as ex: - logger.exception(ex) - break - elif msg.type == web.WSMsgType.ERROR: + rpc = RPCView.RPCApi(self, ws) + async for msg in ws: + if msg.type == web.WSMsgType.TEXT: + try: + await rpc(msg.json()) + except Exception as ex: + print("XXXXXXXXXX Deleting socket", ex, flush=True) + logger.exception(ex) + await self.services.socket.delete(ws) break - elif msg.type == web.WSMsgType.CLOSE: - break - finally: - await self.services.socket.delete(ws) - + elif msg.type == web.WSMsgType.ERROR: + pass + elif msg.type == web.WSMsgType.CLOSE: + pass return ws