The application times out waiting for a connection, while SQL Server appears calm. Connection pool exhaustion can live in the client process, but server sessions still provide useful evidence.

Know Which Side Owns the Pool
Most application connection pools live in the client driver or framework. SQL Server does not know which client-side pool slot is checked out. A pool timeout can occur when code opens connections and fails to close them, when commands hang, or when one pool is fragmented by many distinct connection strings. I start with application metrics and logs, then use server DMVs to identify matching sessions and work. An idle server with many sessions can still accompany a full client pool. An idle server with few sessions can point to a pool limit or an application-side bookkeeping problem.
Record the exact error text, application instance, connection string identity, pool maximum, and time of failure. Those facts narrow the search faster than restarting SQL Server.
Trace Connection Pool Exhaustion to Its Source
sys.dm_exec_sessions exposes host_name, program_name, login_name, status, and login time. Group by host and program to see whether one application node owns an unusual share. These fields come from client metadata, so do not use them as a security identity. I compare the count with a healthy period and with the configured pool size per process. A large session count can be normal for a busy application; the question is whether sessions return to the pool.
SELECT host_name, program_name, login_name, status,
COUNT(*) AS session_count
FROM sys.dm_exec_sessions
WHERE is_user_process = 1
GROUP BY host_name, program_name, login_name, status
ORDER BY session_count DESC;Find Sleepers With Open Work
A sleeping session can still hold an open transaction. Join session transaction DMVs and inspect open_transaction_count. Such a session can block other work and tie up a connection. Check transaction start time and locks before deciding it is abandoned. I have seen a developer assume sleeping means harmless. The locks do not read that label. Do not KILL a session solely because it is old; coordinate with the application owner and capture evidence first.
SELECT s.session_id, s.host_name, s.program_name, s.status,
s.open_transaction_count, at.transaction_begin_time
FROM sys.dm_exec_sessions AS s
JOIN sys.dm_tran_session_transactions AS st
ON st.session_id = s.session_id
JOIN sys.dm_tran_active_transactions AS at
ON at.transaction_id = st.transaction_id
WHERE s.is_user_process = 1
AND s.status = N'sleeping'
ORDER BY at.transaction_begin_time;Read the Last Command Carefully
sys.dm_exec_connections exposes most_recent_sql_handle. Apply sys.dm_exec_sql_text to see the last batch associated with the connection. It is a clue, not proof that the batch caused the leak. The application can reuse a pooled session for different requests. Capture the session ID, connection ID, application correlation ID, and timestamp so developers can match server evidence to their logs. I avoid posting raw SQL text publicly because it can include literals.
SELECT s.session_id, s.host_name, s.program_name,
c.connect_time, st.text AS most_recent_batch
FROM sys.dm_exec_sessions AS s
JOIN sys.dm_exec_connections AS c ON c.session_id = s.session_id
OUTER APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) AS st
WHERE s.is_user_process = 1
ORDER BY c.connect_time;
Hand Developers a Reproducible Connection Pool Exhaustion Case
Group failures by application node and request path. Show pool settings, open connection count over time, active commands, sleeping transactions, and last batches. Ask whether every code path disposes the connection, including exceptions and cancellations. For async code, check that the connection lifetime matches the awaited operation. A connection returned to the pool with an open transaction can cause a different problem on the next checkout. The fix belongs in application lifecycle code, not in a permanent increase to Max Pool Size.
What happens if you temporarily raise the limit? It can postpone the failure and increase server sessions, but it does not repair a leak. Use it only as a controlled mitigation with monitoring.
Investigate Pool Fragmentation
Client libraries commonly keep separate pools for distinct connection strings or identities. Differences in database name, credentials, application name, or even string construction can split traffic into pools. I ask developers to inventory the exact connection strings used by the affected process, with secrets redacted. A process can hit the limit in one pool while other pools are nearly empty. SQL Server's grouped session view can hint at that split, but the client library's pool metrics are the source of truth.
Check whether a burst of new application instances created more pools than expected. A server session count rising after deployment does not automatically mean a leak. Compare process count, pool count, and request rate on the same timeline. I avoid solving a fragmentation problem by increasing the global connection limit.
Separate Leaks From Slow Checkout
A connection held by a blocked command is not leaked, but it still occupies a pool slot. Capture active requests and blocking chains during the pool timeout. If all slots are busy doing long work, fix the query or blocker. If slots remain checked out after requests finish, inspect code paths that miss disposal. If sessions are sleeping with transactions open, find the request that left them. Each pattern has a different owner and fix. A single error message about connection pool exhaustion cannot decide among them.
What does a healthy load test look like? Checked-out count rises with traffic, then falls as requests complete. Wait-for-slot time stays bounded, and SQL session count stabilizes after warmup. I repeat the same test after the fix and include exception and cancellation paths. A leak that appears only when an HTTP request is canceled will hide in a happy-path benchmark.
Before asking developers to increase Max Pool Size, compare the configured limit with requests that actually need simultaneous database work. A larger pool can amplify pressure on SQL Server and hide a slow command. I prefer closing connections promptly and reducing hold time. The load test should show checked-out slots returning after requests finish, not only fewer timeout errors.
Verify the Connection Pool Exhaustion Fix Under Load
Run a realistic load test and watch checked-out pool slots, wait time for a slot, SQL session count, and sleeping open transactions. Counts should stabilize when requests finish. I compare the same request mix before and after the code change. A quiet hour is not a valid proof of a leak fix. Keep the diagnostic query and application metric on one timeline to show that connections return rather than simply accumulating more slowly.
If SQL Server is genuinely overloaded, investigate blocking and slow commands too. A pool can fill because each request holds its connection longer, even when every connection is eventually closed. The server view helps distinguish that from a missing Dispose call.
Related reading on this blog: Finding Open Transactions for Session: @@TRANCOUNT and Sleeping vs Suspended Process: SQL in Sixty Seconds #122.

A pool timeout is not proof the server is busy, it is proof a client could not get a slot.
Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.




