From a2d74c30f9f68552fd3cf7b85f64fef06e4028a4 Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Wed, 16 Sep 2026 13:17:35 +0530 Subject: [PATCH] Close temporary async sessions when requests fail --- tests/unit/http/test_async_http_client.py | 58 +++++++++++++++++++++++ twilio/http/async_http_client.py | 20 ++++---- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/tests/unit/http/test_async_http_client.py b/tests/unit/http/test_async_http_client.py index 23df35dc02..2498c26857 100644 --- a/tests/unit/http/test_async_http_client.py +++ b/tests/unit/http/test_async_http_client.py @@ -1,3 +1,5 @@ +import asyncio + import aiounittest from aiohttp import ClientSession @@ -131,3 +133,59 @@ async def test_session_not_preserved(self): # No session used, responses should be different (not cached) self.assertEqual(response_1.content, "response_1") self.assertEqual(response_2.content, "response_2") + + async def test_temporary_session_closed_after_request_error(self): + self._setup_session_response("unused") + session = self.session_constructor_mock.return_value + session.request.side_effect = OSError("connection failed") + client = AsyncTwilioHttpClient(pool_connections=False) + + with self.assertRaisesRegex(OSError, "connection failed"): + await client.request("GET", "https://api.twilio.com") + + session.close.assert_awaited_once() + + async def test_temporary_session_closed_after_body_error(self): + self._setup_session_response("unused") + session = self.session_constructor_mock.return_value + session.request.return_value.text = AsyncMock( + side_effect=UnicodeDecodeError("utf-8", b"\xff", 0, 1, "invalid byte") + ) + client = AsyncTwilioHttpClient(pool_connections=False) + + with self.assertRaises(UnicodeDecodeError): + await client.request("GET", "https://api.twilio.com") + + session.close.assert_awaited_once() + + async def test_temporary_session_closed_after_cancellation(self): + self._setup_session_response("unused") + session = self.session_constructor_mock.return_value + session.request.side_effect = asyncio.CancelledError() + client = AsyncTwilioHttpClient(pool_connections=False) + + with self.assertRaises(asyncio.CancelledError): + await client.request("GET", "https://api.twilio.com") + + session.close.assert_awaited_once() + + async def test_pooled_session_preserved_after_request_error(self): + self._setup_session_response("unused") + session = self.session_constructor_mock.return_value + session.request.side_effect = OSError("connection failed") + client = AsyncTwilioHttpClient() + + with self.assertRaisesRegex(OSError, "connection failed"): + await client.request("GET", "https://api.twilio.com") + + session.close.assert_not_awaited() + + async def test_temporary_session_closed_after_success(self): + self._setup_session_response("response") + session = self.session_constructor_mock.return_value + client = AsyncTwilioHttpClient(pool_connections=False) + + response = await client.request("GET", "https://api.twilio.com") + + self.assertEqual(response.content, "response") + session.close.assert_awaited_once() diff --git a/twilio/http/async_http_client.py b/twilio/http/async_http_client.py index ecd5d4de95..483c4c436b 100644 --- a/twilio/http/async_http_client.py +++ b/twilio/http/async_http_client.py @@ -104,15 +104,17 @@ async def request( else: session = ClientSession() temp = True - self._test_only_last_request = TwilioRequest(**kwargs) - response = await session.request(**kwargs) - self.log_response(response.status, response) - self._test_only_last_response = Response( - response.status, await response.text(), response.headers - ) - if temp: - await session.close() - return self._test_only_last_response + try: + self._test_only_last_request = TwilioRequest(**kwargs) + response = await session.request(**kwargs) + self.log_response(response.status, response) + self._test_only_last_response = Response( + response.status, await response.text(), response.headers + ) + return self._test_only_last_response + finally: + if temp: + await session.close() async def close(self): """