Repository navigation
Connection pool can briefly exceed max_size when pooling is disabled under load #746
Copy link
Copy link
Open
Labels
area: connectivity-authConnection lifecycle, Entra/SP/NTLM auth, tokens, TLS, conn-string parsing, Fabric endpoints.Connection lifecycle, Entra/SP/NTLM auth, tokens, TLS, conn-string parsing, Fabric endpoints.bugSomething isn't workingSomething isn't workingtriage doneIssues that are triaged by dev team and are in investigation.Issues that are triaged by dev team and are in investigation.under development
Description
Activity
- addedbugSomething isn't workingSomething isn't workingtriage neededFor new issues, not triaged yet.For new issues, not triaged yet.area: connectivity-authConnection lifecycle, Entra/SP/NTLM auth, tokens, TLS, conn-string parsing, Fabric endpoints.Connection lifecycle, Entra/SP/NTLM auth, tokens, TLS, conn-string parsing, Fabric endpoints.
on Sep 3, 2026 Hi Gaurav Sharma (@bewithgaurav), 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!
- added 11 commits that reference this issue
on Sep 17, 2026 - addedtriage doneIssues that are triaged by dev team and are in investigation.Issues that are triaged by dev team and are in investigation.and removedtriage neededFor new issues, not triaged yet.For new issues, not triaged yet.
on Sep 21, 2026
Metadata
Metadata
Assignees
Labels
area: connectivity-authConnection lifecycle, Entra/SP/NTLM auth, tokens, TLS, conn-string parsing, Fabric endpoints.Connection lifecycle, Entra/SP/NTLM auth, tokens, TLS, conn-string parsing, Fabric endpoints.bugSomething isn't workingSomething isn't workingtriage doneIssues that are triaged by dev team and are in investigation.Issues that are triaged by dev team and are in investigation.under development
Describe the bug
The connection pool can transiently exceed its configured
max_sizewhen pooling is disabled (or the process begins shutting down) while new physical connections are being opened concurrently. The pool's internal reserved-capacity counter (_current_size) can drift below the true number of live connections, after which the pool opens more physical connections thanmax_sizeallows. It is transient and self-corrects as connections close.Root cause:
_current_sizeis a bare counter with no record of which reservation a given decrement belongs to. The failed-open cleanup runsif (_current_size > 0) --_current_size, while a pool disable/close runs_current_size = 0. If a reset lands between a thread reserving a slot and that same thread's open failing, the thread's decrement cancels a different thread's live reservation instead of its own.To reproduce
This is a rare timing race, so it is not deterministically reproducible from a plain script. It requires a new connection to fail to open at the exact moment pooling is disabled or the process is shutting down. The interleave that produces it, with
max_size = 2:Observable symptom while the count is drifted: more live sessions than the configured cap.
Expected behavior
The number of live physical connections should never exceed
max_size, regardless of a connection-open failure racing a pooling disable or process shutdown.Further technical details
Python version: any
SQL Server version: any
Operating system: any. The race is in the cross-platform C++ pool, not platform specific.
Additional context
Area:
mssql_python/pybind/connection/connection_pool.cpp, the Phase 3 failure catch inConnectionPool::acquire, plusclosePools()andConnectionPool::close.Regression status: pre-existing for the
connect()-failure path. PR #678 movedConnectionconstruction out of_mutex(required to fix the #671 deadlock), which widens the same race to also cover constructor failures such as ODBC env init, handle allocation, and OOM. Raised during review of #678.Proposed fix: give the pool a generation counter.
close()/closePools()bumps it under the lock, a reserver snapshots it at++_current_size, and the failure cleanup only decrements if the generation still matches. This makes the decrement attributable and closes both the constructor and connect paths. Roughly 6 to 8 lines plus a regression test that forces the interleave.