summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/pool/base.py65
1 files changed, 40 insertions, 25 deletions
diff --git a/lib/sqlalchemy/pool/base.py b/lib/sqlalchemy/pool/base.py
index a77f65205..d67f32442 100644
--- a/lib/sqlalchemy/pool/base.py
+++ b/lib/sqlalchemy/pool/base.py
@@ -382,7 +382,10 @@ class Pool(log.Identified, event.EventTarget):
self._dialect.do_close(connection)
except BaseException as e:
self.logger.error(
- "Exception closing connection %r", connection, exc_info=True
+ f"Exception {'terminating' if terminate else 'closing'} "
+ f"connection %r",
+ connection,
+ exc_info=True,
)
if not isinstance(e, Exception):
raise
@@ -941,27 +944,32 @@ def _finalize_fairy(
if is_gc_cleanup:
assert ref is not None
_strong_ref_connection_records.pop(ref, None)
- elif fairy:
- _strong_ref_connection_records.pop(weakref.ref(fairy), None)
-
- if is_gc_cleanup:
assert connection_record is not None
if connection_record.fairy_ref is not ref:
return
assert dbapi_connection is None
dbapi_connection = connection_record.dbapi_connection
+ elif fairy:
+ _strong_ref_connection_records.pop(weakref.ref(fairy), None)
+
# null pool is not _is_asyncio but can be used also with async dialects
- dont_restore_gced = (
- pool._dialect.is_async and not pool._dialect.has_terminate
- )
+ dont_restore_gced = pool._dialect.is_async
if dont_restore_gced:
detach = connection_record is None or is_gc_cleanup
- can_manipulate_connection = ref is None
+ can_manipulate_connection = not is_gc_cleanup
+ can_close_or_terminate_connection = (
+ not pool._dialect.is_async or pool._dialect.has_terminate
+ )
+ requires_terminate_for_close = (
+ pool._dialect.is_async and pool._dialect.has_terminate
+ )
+
else:
detach = connection_record is None
- can_manipulate_connection = True
+ can_manipulate_connection = can_close_or_terminate_connection = True
+ requires_terminate_for_close = False
if dbapi_connection is not None:
if connection_record and echo:
@@ -992,25 +1000,14 @@ def _finalize_fairy(
fairy._pool = pool
fairy.detach()
- if can_manipulate_connection:
+ if can_close_or_terminate_connection:
if pool.dispatch.close_detached:
pool.dispatch.close_detached(dbapi_connection)
- pool._close_connection(dbapi_connection)
- else:
- message = (
- "The garbage collector is trying to clean up "
- f"connection {dbapi_connection!r}. This feature is "
- "unsupported on asyncio "
- 'dbapis that lack a "terminate" feature, since no '
- "IO can be performed at this stage to "
- "reset the connection. Please close out all "
- "connections when they are no longer used, calling "
- "``close()`` or using a context manager to "
- "manage their lifetime."
+ pool._close_connection(
+ dbapi_connection,
+ terminate=requires_terminate_for_close,
)
- pool.logger.error(message)
- util.warn(message)
except BaseException as e:
pool.logger.error(
@@ -1020,6 +1017,24 @@ def _finalize_fairy(
connection_record.invalidate(e=e)
if not isinstance(e, Exception):
raise
+ finally:
+ if detach and is_gc_cleanup and dont_restore_gced:
+ message = (
+ "The garbage collector is trying to clean up "
+ f"non-checked-in connection {dbapi_connection!r}, "
+ f"""which will be {
+ 'dropped, as it cannot be safely terminated'
+ if not can_close_or_terminate_connection
+ else 'terminated'
+ }. """
+ "Please ensure that SQLAlchemy pooled connections are "
+ "returned to "
+ "the pool explicitly, either by calling ``close()`` "
+ "or by using appropriate context managers to manage "
+ "their lifecycle."
+ )
+ pool.logger.error(message)
+ util.warn(message)
if connection_record and connection_record.fairy_ref is not None:
connection_record.checkin()