psycopg/psycopg2 · error · PoolError

trying to put unkeyed connection

Error message

trying to put unkeyed connection

What it means

Raised by AbstractConnectionPool._putconn() (lib/pool.py:102-103) when putconn() is called with a connection that has no entry in _rused (the id(conn) -> key map). This means the connection was never checked out from this pool (or was already returned), so the pool cannot reconcile its bookkeeping and refuses the return.

Source

Thrown at lib/pool.py:103

        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:
                    # server connection lost
                    conn.close()
                elif status != _ext.TRANSACTION_STATUS_IDLE:
                    # connection in error or in transaction
                    conn.rollback()
                    self._pool.append(conn)
                else:
                    # regular idle connection
                    self._pool.append(conn)
            # If the connection is closed, we just discard it.
        else:

View on GitHub (pinned to 3a6d9d6ddc)

Solutions

  1. Only return connections acquired from the same pool via getconn().
  2. Capture and reuse the key returned/used at getconn time, passing it explicitly to putconn(conn, key).
  3. Guard against double-return by setting the connection variable to None after returning it.
  4. Ensure a single pool instance is used; do not mix pool-managed and manually-created connections.

Example fix

// before
conn = psycopg2.connect(...)
pool.putconn(conn)  # never came from pool
// after
conn = pool.getconn()
try: use(conn)
finally: pool.putconn(conn)
Defensive patterns

Strategy: validation

Validate before calling

key = pool._rused.get(id(conn))
if key is None:
    raise RuntimeError('conn was not checked out from this pool')
pool.putconn(conn, key)

Type guard

def conn_belongs_to_pool(pool, conn) -> bool:
    return id(conn) in pool._rused

Try / catch

from psycopg2.pool import PoolError
try:
    pool.putconn(conn, key)
except PoolError as e:
    if 'unkeyed' in str(e):
        # not from this pool; close it directly
        try: conn.close()
        except Exception: pass
    else: raise

Prevention

When it happens

Trigger: Returning a connection obtained elsewhere (not from this pool), returning the same connection twice, or returning a connection after the pool's _used/_rused entries were already cleared (e.g. after closeall cleared state). Calling putconn(conn) without a key relies on _rused lookup at line 101.

Common situations: Returning a connection created by psycopg2.connect() directly instead of pool.getconn(). Double-return bugs where a finally block runs twice. Mixing connections between two different pool instances.

Related errors


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