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
- Only return connections acquired from the same pool via getconn().
- Capture and reuse the key returned/used at getconn time, passing it explicitly to putconn(conn, key).
- Guard against double-return by setting the connection variable to None after returning it.
- 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
- Capture the key at getconn time and pass it to putconn explicitly.
- Set conn = None after putconn to prevent double-return.
- Never return connections not acquired from the same pool.
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.