The Fix
Addresses issue #11091 by adding a configurable `ssl_shutdown_timeout` parameter to control the SSL shutdown handshake timeout, fixing the 30-second blocking issue when closing SSL connections.
Based on closed aio-libs/aiohttp issue #11091 · PR/commit linked
Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+Added ``ssl_shutdown_timeout`` parameter to :py:class:`~aiohttp.ClientSession` and :py:class:`~aiohttp.TCPConnector` to control the grace period for SSL shutdown handshake on TLS connections. This helps prevent "connection reset" errors on the server side while avoiding excessive delays during connector cleanup. Note: This parameter only takes effect on Python 3.11+ -- by :user:`bdraco`.
diff --git a/CHANGES/11094.feature.rst b/CHANGES/11094.feature.rst
new file mode 120000
import asyncio
import logging
from azure.data.tables.aio import TableServiceClient
logger = logging.getLogger(__name__)
async def create_table_with_async_table_service_client() -> None:
logger.info("Creating table asynchronously...")
async with TableServiceClient.from_connection_string("<connection_string>") as table_service_client:
await table_service_client.create_table_if_not_exists("TestTable")
logger.info("TestTable created successfully or already exists")
logger.info("AsyncTableServiceClient exited successfully")
async def do_nothing_in_async_table_service_client() -> None:
logger.info("Doing nothing in AsyncTableServiceClient...")
async with AsyncTableServiceClient.from_connection_string("<connection_string>"):
logger.info("AsyncTableServiceClient is ready for use - doing nothing")
logger.info("AsyncTableServiceClient exited successfully")
async def main() -> None:
logging.basicConfig(level=logging.INFO)
await create_table_with_async_table_service_client()
await do_nothing_in_async_table_service_client()
if __name__ == "__main__":
asyncio.run(main())
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Apply the official fix\nAddresses issue #11091 by adding a configurable `ssl_shutdown_timeout` parameter to control the SSL shutdown handshake timeout, fixing the 30-second blocking issue when closing SSL connections.\nWhen NOT to use: Do not use if it changes public behavior or if the failure cannot be reproduced.\n\n
Why This Fix Works in Production
- Trigger: DEBUG:asyncio:Using selector: EpollSelector
- Mechanism: The SSL shutdown handshake can timeout when the remote server doesn't respond, causing delays
- If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.
Why This Breaks in Prod
- The SSL shutdown handshake can timeout when the remote server doesn't respond, causing delays
- Surfaces as: DEBUG:asyncio:Using selector: EpollSelector
Proof / Evidence
- GitHub issue: #11091
- Fix PR: https://github.com/aio-libs/aiohttp/pull/11094
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.70
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.45
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“We completely understand that this can cause problems in real-world scenarios”
“Repro: Stolen from here: https://github.com/Azure/azure-sdk-for-python/issues/41363 Maybe some folks from here can pile onto the azure issue.”
“Asking the maintainers of those projects that misbehave might help, since those people would know better how to distill a reproducer from their side to…”
“~~Thanks for sharing the repro”
Failure Signature (Search String)
- DEBUG:asyncio:Using selector: EpollSelector
Error Message
Stack trace
Error Message
-------------
DEBUG:asyncio:Using selector: EpollSelector
Status: 200
Streaming response:
ERROR:root:Error while closing connector: ClientConnectionError('Connection lost: SSL shutdown timed out')
Minimal Reproduction
import asyncio
import logging
from azure.data.tables.aio import TableServiceClient
logger = logging.getLogger(__name__)
async def create_table_with_async_table_service_client() -> None:
logger.info("Creating table asynchronously...")
async with TableServiceClient.from_connection_string("<connection_string>") as table_service_client:
await table_service_client.create_table_if_not_exists("TestTable")
logger.info("TestTable created successfully or already exists")
logger.info("AsyncTableServiceClient exited successfully")
async def do_nothing_in_async_table_service_client() -> None:
logger.info("Doing nothing in AsyncTableServiceClient...")
async with AsyncTableServiceClient.from_connection_string("<connection_string>"):
logger.info("AsyncTableServiceClient is ready for use - doing nothing")
logger.info("AsyncTableServiceClient exited successfully")
async def main() -> None:
logging.basicConfig(level=logging.INFO)
await create_table_with_async_table_service_client()
await do_nothing_in_async_table_service_client()
if __name__ == "__main__":
asyncio.run(main())
What Broke
Users experience a 30-second delay when closing SSL connections to streaming endpoints.
Why It Broke
The SSL shutdown handshake can timeout when the remote server doesn't respond, causing delays
Fix Options (Details)
Option A — Apply the official fix
Addresses issue #11091 by adding a configurable `ssl_shutdown_timeout` parameter to control the SSL shutdown handshake timeout, fixing the 30-second blocking issue when closing SSL connections.
Fix reference: https://github.com/aio-libs/aiohttp/pull/11094
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- Do not use if it changes public behavior or if the failure cannot be reproduced.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Track RSS + object counts after deployments; alert on monotonic growth and GC pressure.
- Add a long-running test that repeats the failing call path and asserts stable memory.
- Make timeouts explicit and test them (unit + integration) to avoid silent behavior changes.
- Instrument retries (attempt count + reason) and alert on spikes to catch dependency slowdowns.
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.