From e5b0daf639bb62732d5cf8223c63d10b74961ee2 Mon Sep 17 00:00:00 2001 From: BADtochka Date: Sat, 6 Jun 2026 15:39:34 +0300 Subject: [PATCH] fix(payments): avoid stale provider connections --- .../payment_providers/shared/http_client.py | 11 +++++++++-- tests/test_payment_http_client.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 tests/test_payment_http_client.py diff --git a/backend/bot/payment_providers/shared/http_client.py b/backend/bot/payment_providers/shared/http_client.py index c5dbd6d..b8cb1d4 100644 --- a/backend/bot/payment_providers/shared/http_client.py +++ b/backend/bot/payment_providers/shared/http_client.py @@ -4,7 +4,7 @@ import json import logging from typing import Any, Callable, Dict, Mapping, Optional, Tuple -from aiohttp import ClientSession, ClientTimeout +from aiohttp import ClientSession, ClientTimeout, TCPConnector SuccessCheck = Callable[[int, Any], bool] @@ -76,18 +76,25 @@ class HttpClientMixin: Each subclass calls ``self._init_http_client(total_timeout=...)`` from ``__init__`` and inherits ``_get_session`` / ``close``. The session is created on first use and recreated transparently if it was closed. + + Payment provider calls are infrequent but user-facing, so the default + connector does not reuse TCP connections. This avoids intermittent hangs + on stale keep-alive sockets after long idle periods. """ _timeout: ClientTimeout _session: Optional[ClientSession] + _connector_force_close: bool def _init_http_client(self, *, total_timeout: float = 20.0) -> None: self._timeout = ClientTimeout(total=total_timeout) self._session = None + self._connector_force_close = True async def _get_session(self) -> ClientSession: if self._session is None or self._session.closed: - self._session = ClientSession(timeout=self._timeout) + connector = TCPConnector(force_close=self._connector_force_close) + self._session = ClientSession(timeout=self._timeout, connector=connector) return self._session async def close(self) -> None: diff --git a/tests/test_payment_http_client.py b/tests/test_payment_http_client.py new file mode 100644 index 0000000..4fbfea8 --- /dev/null +++ b/tests/test_payment_http_client.py @@ -0,0 +1,18 @@ +import unittest + +from bot.payment_providers.shared.http_client import HttpClientMixin + + +class _DummyHttpClient(HttpClientMixin): + def __init__(self): + self._init_http_client(total_timeout=20) + + +class PaymentHttpClientTests(unittest.IsolatedAsyncioTestCase): + async def test_http_client_does_not_reuse_provider_tcp_connections(self): + client = _DummyHttpClient() + try: + session = await client._get_session() + self.assertTrue(session.connector.force_close) + finally: + await client.close()