Repository navigation
Pooled connection checkout has no timeout: connect() hangs ~16 min on a dead socket #854
Description
Describe the bug
With pooling enabled, if a pooled connection's TCP socket has been silently dropped (no FIN/RST), the next connect() that reuses it blocks until the OS gives up on the socket. On Linux with the default net.ipv4.tcp_retries2=15 that is ~16 minutes. The login timeout doesn't apply. Once the socket errors, the pool opens a new connection and connect() returns successfully.
If several pooled connections are dead, one connect() checks them one after another.
From the source, isAlive() only reads the cached SQL_ATTR_CONNECTION_DEAD flag, so a dead socket passes it. reset() then sets SQL_ATTR_TXN_ISOLATION, which appears to be the call that waits on the server, with no timeout.
To reproduce
Client: python:3.12-slim-trixie container (linux/amd64), mssql-python==1.16.0, run with --cap-add NET_ADMIN. Server: mcr.microsoft.com/mssql/server:2022-latest, reachable as sqlserver.
import os, subprocess, time
import mssql_python
mssql_python.pooling(idle_timeout=600)
kw = dict(Server="sqlserver", UID=os.environ["DB_USER"], PWD=os.environ["DB_PASSWORD"], Encrypt="yes",
TrustServerCertificate="yes", timeout=15, autocommit=True)
conn = mssql_python.connect(**kw)
cur = conn.cursor()
cur.execute("select client_tcp_port from sys.dm_exec_connections where session_id = @@SPID")
port = cur.fetchone()[0]
conn.close() # back to the pool
# silently drop that connection's traffic (DROP, so no RST is sent)
subprocess.run(["iptables", "-A", "OUTPUT", "-p", "tcp", "--sport", str(port), "-j", "DROP"], check=True)
subprocess.run(["iptables", "-A", "INPUT", "-p", "tcp", "--dport", str(port), "-j", "DROP"], check=True)
time.sleep(8)
t = time.monotonic()
mssql_python.connect(**kw).close() # reuses the dead pooled connection
print(f"connect() took {time.monotonic() - t:.1f}s")Results:
| Case | connect() duration |
|---|---|
tcp_retries2=15 (Linux default) |
951.8 s |
tcp_retries2=5 (--sysctl net.ipv4.tcp_retries2=5) |
~13 s |
3 dead pooled connections, tcp_retries2=5 |
29.5 s |
| Pooling disabled | 0.02 s |
The same happens on 1.15.0, and with autocommit=False. It doesn't happen if the socket was dead long enough for TCP keepalive to mark it dead first (~40 s with the default KeepAlive=30).
Expected behavior
Checking out a pooled connection should be bounded in time, for example by the login timeout or a separate validation timeout, with a timed-out candidate treated as dead. Alternatively, the checkout could avoid a server round trip. At minimum, the behaviour should be documented.
Further technical details
- Python version: 3.12.15
- mssql-python: 1.15.0 and 1.16.0
- SQL Server version: SQL Server 2022 (repro); also seen against Azure SQL Managed Instance
- Operating system: Debian 13 (trixie),
linux/amd64
Additional context
The investigation, repro and analysis were done with the help of Claude Opus 5.5. The results above were measured; the root-cause reading of the source is our interpretation and may be wrong.
Activity
github-actions commentedon Oct 20, 2026
Hi Dimitris Lygkonis (@xyres137), thank you for opening this issue!
Our team will review it shortly. We aim to triage all new issues within 24-48 hours and get back to you.
If you have additional information to share, please feel free to update the issue.
Thank you for your patience!