From a3c95d044b1f102671f0823001a4900486bdbd58 Mon Sep 17 00:00:00 2001 From: Darafei Praliaskouski Date: Fri, 7 Aug 2026 03:24:54 +0400 Subject: [PATCH 1/5] fix(websocket): preserve current backoff lifecycle state --- app/modules/proxy/_service/websocket/mixin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/modules/proxy/_service/websocket/mixin.py b/app/modules/proxy/_service/websocket/mixin.py index 50308cbc78..0d4bc39226 100644 --- a/app/modules/proxy/_service/websocket/mixin.py +++ b/app/modules/proxy/_service/websocket/mixin.py @@ -1727,6 +1727,7 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: ("previous response", previous_response_owner_account_id), ) except ProxyResponseError as exc: + response_create_request_state = request_state error = _parse_openai_error(exc.payload) error_code = _normalize_error_code( error.code if error else None, @@ -1735,7 +1736,7 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: error_message = error.message if error and error.message else "Upstream error" error_type = error.type if error and error.type else "server_error" error_param = error.param if error else None - await proxy._release_websocket_request_state_reservation(request_state) + await proxy._release_websocket_request_state_reservation(response_create_request_state) await proxy._write_websocket_connect_failure( account_id=None, api_key=api_key, From 1676031f4c6818cef61ba2891b530eea92400b13 Mon Sep 17 00:00:00 2001 From: Darafei Praliaskouski Date: Fri, 7 Aug 2026 04:20:37 +0400 Subject: [PATCH 2/5] fix(api-keys): record last-used timestamp --- app/modules/api_keys/repository.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/modules/api_keys/repository.py b/app/modules/api_keys/repository.py index 8fb9b77a83..cb13853785 100644 --- a/app/modules/api_keys/repository.py +++ b/app/modules/api_keys/repository.py @@ -106,6 +106,13 @@ class ApiKeysRepository: def __init__(self, session: AsyncSession) -> None: self._session = session + async def update_last_used(self, key_id: str, *, commit: bool = True) -> None: + await self._session.execute( + update(ApiKey).where(ApiKey.id == key_id).values(last_used_at=utcnow()) + ) + if commit: + await self._session.commit() + @staticmethod def _build_account_costs(rows: Sequence[object]) -> list[ApiKeyAccountCost]: account_costs: list[ApiKeyAccountCost] = [] From 2bdd2b68ee231035aa6bf4fe603c174ea272b8de Mon Sep 17 00:00:00 2001 From: Darafei Praliaskouski Date: Fri, 7 Aug 2026 05:55:20 +0400 Subject: [PATCH 3/5] fix websocket reconnect cancellation ownership --- app/modules/proxy/_service/websocket/mixin.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/modules/proxy/_service/websocket/mixin.py b/app/modules/proxy/_service/websocket/mixin.py index 0d4bc39226..89586153af 100644 --- a/app/modules/proxy/_service/websocket/mixin.py +++ b/app/modules/proxy/_service/websocket/mixin.py @@ -1676,8 +1676,18 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: and upstream_control is not None and upstream_control.reconnect_requested and upstream_reader is not None + and ( + upstream_reader.done() + or upstream_control.replay_request_state is not None + or not pending_requests + ) ): - await upstream_reader + try: + await upstream_reader + except asyncio.CancelledError: + current_task = asyncio.current_task() + if current_task is not None and current_task.cancelling(): + raise if replay_request_state is None: replay_request_state = upstream_control.replay_request_state upstream_reader = None From f5692a8f5ca5643398aa22ec8eed7c0721ec8aab Mon Sep 17 00:00:00 2001 From: Darafei Praliaskouski Date: Fri, 7 Aug 2026 06:05:56 +0400 Subject: [PATCH 4/5] style: format api key usage update --- app/modules/api_keys/repository.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/modules/api_keys/repository.py b/app/modules/api_keys/repository.py index cb13853785..4467ddbdad 100644 --- a/app/modules/api_keys/repository.py +++ b/app/modules/api_keys/repository.py @@ -107,9 +107,7 @@ def __init__(self, session: AsyncSession) -> None: self._session = session async def update_last_used(self, key_id: str, *, commit: bool = True) -> None: - await self._session.execute( - update(ApiKey).where(ApiKey.id == key_id).values(last_used_at=utcnow()) - ) + await self._session.execute(update(ApiKey).where(ApiKey.id == key_id).values(last_used_at=utcnow())) if commit: await self._session.commit() From b73e361ad30181bcb64afa69131143a26e9b3c01 Mon Sep 17 00:00:00 2001 From: Darafei Praliaskouski Date: Fri, 7 Aug 2026 06:10:51 +0400 Subject: [PATCH 5/5] docs: clarify reconnect drain ownership gate --- app/modules/proxy/_service/websocket/mixin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/modules/proxy/_service/websocket/mixin.py b/app/modules/proxy/_service/websocket/mixin.py index 89586153af..9fbdb0e900 100644 --- a/app/modules/proxy/_service/websocket/mixin.py +++ b/app/modules/proxy/_service/websocket/mixin.py @@ -1677,6 +1677,9 @@ def take_reader_replay_request_state() -> _WebSocketRequestState | None: and upstream_control.reconnect_requested and upstream_reader is not None and ( + # Keep receiving downstream controls while a live + # reader still owns pending work; only replay or a + # completed/empty drain needs immediate settlement. upstream_reader.done() or upstream_control.replay_request_state is not None or not pending_requests