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.
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: /api/errors/4aea97e5406f62ca.
Report an issue: GitHub.
Appendix: 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)