StreamChatAsync does not clean up resources according to httpaio
Summary:
httpaio generates error messages due to an unclosed connection.
Having taken a quick look at stream_chat/async_chat/client.py, calling session.close() on aexit does not satisfy httpaio in my scenario.
Environment: AWS Lambda AWS Powertools v2.34.2 via AsyncBatchProcessor stream-chat v4.12.1 Python v3.11
Relevant error messages:
"2024-03-25T22:26:43.353Z","[ERROR] 2024-03-25T22:26:43.353Z 2f7ab88d-c683-46f7-b6dc-616ab9f3241f Unclosed client session"
"2024-03-25T22:26:43.353Z","connector: <aiohttp.connector.TCPConnector object at 0x7fdf32456f50>"
"2024-03-25T22:26:43.353Z","connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7fdf1a99a970>, 331.795931377)]']"
"2024-03-25T22:26:43.353Z","[ERROR] 2024-03-25T22:26:43.353Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Unclosed connector"
Verbose log:
Date,Message
"2024-03-25T22:26:43.407Z","REPORT RequestId: 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Duration: 499.26 ms Billed Duration: 500 ms Memory Size: 1024 MB Max Memory Used: 395 MB"
"2024-03-25T22:26:43.407Z","END RequestId: 2f7ab88c-b683-46f7-b6dc-616ab9f3241f"
"2024-03-25T22:26:43.404Z","[WARNING] 2024-03-25T22:26:43.404Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Executing <Task finished name='Task-6' coro=<AsyncBatchProcessor._async_process_record() done, defined at /var/task/aws_lambda_powertools/utilities/batch/base.py:634> result=('success', ...) created at /var/lang/lib/python3.11/asyncio/tasks.py:680> took 0.475 seconds"
"2024-03-25T22:26:43.353Z","super().__init__("
"2024-03-25T22:26:43.353Z","File ""/var/task/aiohttp/connector.py"", line 776, in __init__"
"2024-03-25T22:26:43.353Z","connector=aiohttp.TCPConnector(keepalive_timeout=59.0),"
"2024-03-25T22:26:43.353Z","File ""/var/task/stream_chat/async_chat/client.py"", line 68, in __init__"
"2024-03-25T22:26:43.353Z","client = StreamChatAsync("
"2024-03-25T22:26:43.353Z","File ""/var/task/helpers/streamchat_client.py"", line 18, in __init__"
"2024-03-25T22:26:43.353Z","stream_client = StreamChatClient(completionRequested.conversation.channelId)"
"2024-03-25T22:26:43.353Z","File ""/var/task/categorizer.py"", line 56, in async_record_handler"
"2024-03-25T22:26:43.353Z","result = await self.handler(record=data)"
"2024-03-25T22:26:43.353Z","File ""/var/task/aws_lambda_powertools/utilities/batch/base.py"", line 649, in _async_process_record"
"2024-03-25T22:26:43.353Z","self._context.run(self._callback, *self._args)"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/events.py"", line 84, in _run"
"2024-03-25T22:26:43.353Z","handle._run()"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/base_events.py"", line 1928, in _run_once"
"2024-03-25T22:26:43.353Z","self._run_once()"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/base_events.py"", line 608, in run_forever"
"2024-03-25T22:26:43.353Z","self.run_forever()"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/base_events.py"", line 641, in run_until_complete"
"2024-03-25T22:26:43.353Z","return loop.run_until_complete(task_instance)"
"2024-03-25T22:26:43.353Z","File ""/var/task/aws_lambda_powertools/utilities/batch/base.py"", line 126, in async_process"
"2024-03-25T22:26:43.353Z","processor.async_process()"
"2024-03-25T22:26:43.353Z","File ""/var/task/aws_lambda_powertools/utilities/batch/decorators.py"", line 251, in async_process_partial_response"
"2024-03-25T22:26:43.353Z","return async_process_partial_response("
"2024-03-25T22:26:43.353Z","File ""/var/task/categorizer.py"", line 103, in handler"
"2024-03-25T22:26:43.353Z","response = request_handler(event, lambda_context)"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/site-packages/awslambdaric/bootstrap.py"", line 188, in handle_event_request"
"2024-03-25T22:26:43.353Z","handle_event_request("
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/site-packages/awslambdaric/bootstrap.py"", line 499, in run"
"2024-03-25T22:26:43.353Z","bootstrap.run(app_root, handler, lambda_runtime_api_addr)"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/site-packages/awslambdaric/__main__.py"", line 21, in main"
"2024-03-25T22:26:43.353Z","awslambdaricmain.main([os.environ[""LAMBDA_TASK_ROOT""], os.environ[""_HANDLER""]])"
"2024-03-25T22:26:43.353Z","File ""/var/runtime/bootstrap.py"", line 60, in main"
"2024-03-25T22:26:43.353Z","main()"
"2024-03-25T22:26:43.353Z","File ""/var/runtime/bootstrap.py"", line 63, in <module>"
"2024-03-25T22:26:43.353Z","source_traceback: Object created at (most recent call last):"
"2024-03-25T22:26:43.353Z","connector: <aiohttp.connector.TCPConnector object at 0x7fdf32456f50>"
"2024-03-25T22:26:43.353Z","connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x7fdf1a99a970>, 331.795931377)]']"
"2024-03-25T22:26:43.353Z","[ERROR] 2024-03-25T22:26:43.353Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Unclosed connector"
"2024-03-25T22:26:43.353Z","self.session = aiohttp.ClientSession("
"2024-03-25T22:26:43.353Z","File ""/var/task/stream_chat/async_chat/client.py"", line 66, in __init__"
"2024-03-25T22:26:43.353Z","client = StreamChatAsync("
"2024-03-25T22:26:43.353Z","File ""/var/task/helpers/streamchat_client.py"", line 18, in __init__"
"2024-03-25T22:26:43.353Z","stream_client = StreamChatClient(completionRequested.conversation.channelId)"
"2024-03-25T22:26:43.353Z","File ""/var/task/categorizer.py"", line 56, in async_record_handler"
"2024-03-25T22:26:43.353Z","result = await self.handler(record=data)"
"2024-03-25T22:26:43.353Z","File ""/var/task/aws_lambda_powertools/utilities/batch/base.py"", line 649, in _async_process_record"
"2024-03-25T22:26:43.353Z","self._context.run(self._callback, *self._args)"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/events.py"", line 84, in _run"
"2024-03-25T22:26:43.353Z","handle._run()"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/base_events.py"", line 1928, in _run_once"
"2024-03-25T22:26:43.353Z","self._run_once()"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/base_events.py"", line 608, in run_forever"
"2024-03-25T22:26:43.353Z","self.run_forever()"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/asyncio/base_events.py"", line 641, in run_until_complete"
"2024-03-25T22:26:43.353Z","return loop.run_until_complete(task_instance)"
"2024-03-25T22:26:43.353Z","File ""/var/task/aws_lambda_powertools/utilities/batch/base.py"", line 126, in async_process"
"2024-03-25T22:26:43.353Z","processor.async_process()"
"2024-03-25T22:26:43.353Z","File ""/var/task/aws_lambda_powertools/utilities/batch/decorators.py"", line 251, in async_process_partial_response"
"2024-03-25T22:26:43.353Z","return async_process_partial_response("
"2024-03-25T22:26:43.353Z","File ""/var/task/categorizer.py"", line 103, in handler"
"2024-03-25T22:26:43.353Z","response = request_handler(event, lambda_context)"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/site-packages/awslambdaric/bootstrap.py"", line 188, in handle_event_request"
"2024-03-25T22:26:43.353Z","handle_event_request("
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/site-packages/awslambdaric/bootstrap.py"", line 499, in run"
"2024-03-25T22:26:43.353Z","bootstrap.run(app_root, handler, lambda_runtime_api_addr)"
"2024-03-25T22:26:43.353Z","File ""/var/lang/lib/python3.11/site-packages/awslambdaric/__main__.py"", line 21, in main"
"2024-03-25T22:26:43.353Z","awslambdaricmain.main([os.environ[""LAMBDA_TASK_ROOT""], os.environ[""_HANDLER""]])"
"2024-03-25T22:26:43.353Z","File ""/var/runtime/bootstrap.py"", line 60, in main"
"2024-03-25T22:26:43.353Z","main()"
"2024-03-25T22:26:43.353Z","File ""/var/runtime/bootstrap.py"", line 63, in <module>"
"2024-03-25T22:26:43.353Z","source_traceback: Object created at (most recent call last):"
"2024-03-25T22:26:43.353Z","client_session: <aiohttp.client.ClientSession object at 0x7fdf1c022190>"
"2024-03-25T22:26:43.353Z","[ERROR] 2024-03-25T22:26:43.353Z 2f7ab88d-c683-46f7-b6dc-616ab9f3241f Unclosed client session"
"2024-03-25T22:26:42.930Z","[WARNING] 2024-03-25T22:26:42.930Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Could not send typing start event to stream chat - StreamChat error code 16: SendEvent failed with error: ""Can't find channel with id"""""
"2024-03-25T22:26:42.911Z","[INFO] 2024-03-25T22:26:42.911Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Sending typing start event to stream chat channel"
"2024-03-25T22:26:42.908Z","[INFO] 2024-03-25T22:26:42.908Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Received 1 records."
"2024-03-25T22:26:42.908Z","[INFO] 2024-03-25T22:26:42.908Z 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Starting handler..."
"2024-03-25T22:26:42.907Z","START RequestId: 2f7ab88c-b683-46f7-b6dc-616ab9f3241f Version: 75"
I can confirm that this issue still persists, even a year after it was first reported. While we understand that priorities vary, it's a bit surprising to see this still unresolved — especially for a paid product.
The unclosed session warnings continue to appear and require manual workarounds on our side. It would be great to see this addressed in a future update. Thanks in advance for looking into it.
@kanat The issue lies in the fact that an active aiohttp.ClientSession is created in the class init, but it is only closed if the class is used via an async context manager.
This is a flawed design, because it's entirely expected that — due to a some error or just a business logic condition — the async request method might never be called. In such cases, the session remains unclosed, leading to warnings and potential resource leaks.
Option A:
To align with the intended lifecycle of the context manager, the session should be created inside __aenter__, not during __init__. This would guarantee proper cleanup via __aexit__, even if no request methods are invoked.
Option B: Ensures the aiohttp.ClientSession is properly closed when the object is garbage collected.
def __del__(self):
if not self.session.closed:
loop = asyncio.get_event_loop()
if loop.is_running():
loop.create_task(self.close())
else:
loop.run_until_complete(self.close())
Can confirm that the issue is still there, the solution that @OleksandrKravchenko2024 has shared fixes the error; but it's weird that I have to handle this manually.