psycopg/psycopg2 · error · PoolError

connection pool is closed

Error message

connection pool is closed

What it means

Raised by AbstractConnectionPool._getconn() (lib/pool.py:78-79) when getconn() is called on a pool whose 'closed' flag is True. closeall() sets closed=True (lib/pool.py:144); after that the pool refuses to hand out connections because all underlying connections have been closed and the pool is decommissioned.

Source

Thrown at lib/pool.py:79

    def _connect(self, key=None):
        """Create a new connection and assign it to 'key' if not None."""
        conn = psycopg2.connect(*self._args, **self._kwargs)
        if key is not None:
            self._used[key] = conn
            self._rused[id(conn)] = key
        else:
            self._pool.append(conn)
        return conn

    def _getkey(self):
        """Return a new unique key."""
        self._keys += 1
        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:

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Order shutdown so that closeall() is called only after all workers have released their connections.
  2. Guard getconn() callers with a check of pool.closed, or wrap acquisition in try/except psycopg2.pool.PoolError.
  3. In multi-threaded code, use a coordination point (e.g. an event) so threads stop requesting before closeall().
  4. Create a fresh pool for a new lifecycle instead of reusing a closed one.

Example fix

// before
pool.closeall()
conn = pool.getconn()  # shutdown race
// after
# drain workers first, then:
for t in workers: t.join()
pool.closeall()
Defensive patterns

Strategy: validation

Validate before calling

if pool.closed:
    raise RuntimeError('cannot getconn from a closed pool; create a new pool')
conn = pool.getconn()

Type guard

def pool_is_usable(pool) -> bool:
    return not getattr(pool, 'closed', False)

Try / catch

try:
    conn = pool.getconn()
except psycopg2.pool.PoolError as e:
    if 'closed' in str(e):
        pool = create_new_pool(...)
        conn = pool.getconn()
    else: raise

Prevention

When it happens

Trigger: Calling pool.getconn() (or _getconn) after pool.closeall() has been invoked. In threaded pools (ThreadedConnectionPool) this can happen when one thread closes the pool while another tries to acquire a connection.

Common situations: Application shutdown sequences where closeall() runs before all worker threads have finished, or request handlers that lazily grab a connection after a background health-check closed the pool. Also seen in test teardown that closes the pool before fixtures release their connections.

Related errors


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