psycopg/psycopg2 · error · PoolError

connection pool exhausted

Error message

connection pool exhausted

What it means

Raised by AbstractConnectionPool._getconn() (lib/pool.py:91-92) when the pool has no idle connections in _pool and the number of currently checked-out connections (len(_used)) has reached maxconn. The pool cannot create a new connection without exceeding its configured maximum, so it refuses the request rather than silently growing.

Source

Thrown at lib/pool.py:92

        return self._keys

    def _getconn(self, key=None):
        """Get a free connection and assign it to 'key' if not None."""
        if self.closed:
            raise PoolError("connection pool is closed")
        if key is None:
            key = self._getkey()

        if key in self._used:
            return self._used[key]

        if self._pool:
            self._used[key] = conn = self._pool.pop()
            self._rused[id(conn)] = key
            return conn
        else:
            if len(self._used) == self.maxconn:
                raise PoolError("connection pool exhausted")
            return self._connect(key)

    def _putconn(self, conn, key=None, close=False):
        """Put away a connection."""
        if self.closed:
            raise PoolError("connection pool is closed")

        if key is None:
            key = self._rused.get(id(conn))
            if key is None:
                raise PoolError("trying to put unkeyed connection")

        if len(self._pool) < self.minconn and not close:
            # Return the connection into a consistent state before putting
            # it back into the pool
            if not conn.closed:
                status = conn.info.transaction_status
                if status == _ext.TRANSACTION_STATUS_UNKNOWN:

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Ensure every getconn() is paired with a putconn() in a try/finally block.
  2. Raise maxconn to match peak concurrency.
  3. Audit for connection leaks: log len(pool._used) periodically or use a context manager wrapper.
  4. Shorten transaction lifetimes so connections return to the pool faster.
  5. Switch to a pool that blocks/waits (or implement a semaphore) instead of failing fast if bursty traffic is expected.

Example fix

// before
conn = pool.getconn()
use(conn)  # exception here leaks the connection
// after
conn = pool.getconn()
try:
    use(conn)
finally:
    pool.putconn(conn)
Defensive patterns

Strategy: try-catch

Validate before calling

if len(pool._used) >= pool.maxconn:
    # pool will be exhausted; back off or raise early
    raise RuntimeError('pool at maxconn; possible leak')

Type guard

def pool_has_capacity(pool) -> bool:
    return len(pool._used) < pool.maxconn

Try / catch

from psycopg2.pool import PoolError
try:
    conn = pool.getconn()
except PoolError as e:
    if 'exhausted' in str(e):
        # retry after a short delay or reject the request
        time.sleep(retry_delay)
        conn = pool.getconn()
    else: raise

Prevention

When it happens

Trigger: Calling getconn() more than maxconn times without returning connections via putconn(). Each getconn() without a matching putconn() adds to _used; when len(_used) == maxconn and _pool is empty, this error fires.

Common situations: Connection leaks — code that forgets to call putconn() (e.g. an exception skips the finally block). Long-running transactions holding connections. Under-provisioned maxconn for the workload concurrency. In threaded pools, more concurrent workers than maxconn.

Related errors


AI-assisted analysis of psycopg/psycopg2@3a6d9d6ddc (2026-08-04). Data as JSON: /data/errors/1cf9cc85fb62999f.json. Report an issue: GitHub.