summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfowczrek <f.a.owczarek@gmail.com>2025-02-08 14:19:58 +0100
committernessita <124304+nessita@users.noreply.github.com>2025-03-17 18:28:20 -0300
commit6a9db1e626952ffb37633586e6647319ea53467d (patch)
tree27e034a9b91ba81620f70cd4b9491ac743622a6e
parente804a07d76fc85468f27f7130ae1442fabcd650d (diff)
Fixed #34865 -- Released memory earlier than garbage collection on database wrapping layers.
Thank you Florian Apolloner, Jake Howard and Patryk Zawadzki for the clarifying comments and reviews.
-rw-r--r--AUTHORS1
-rw-r--r--django/db/backends/base/client.py3
-rw-r--r--django/db/backends/base/creation.py3
-rw-r--r--django/db/backends/base/features.py3
-rw-r--r--django/db/backends/base/introspection.py3
-rw-r--r--django/db/backends/base/operations.py3
-rw-r--r--django/db/backends/base/validation.py3
-rw-r--r--django/db/utils.py3
-rw-r--r--tests/backends/base/test_base.py31
9 files changed, 53 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
index bd972b16c7..956a5e06f1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -349,6 +349,7 @@ answer newbie questions, and generally made Django that much better:
Federico Capoano <nemesis@ninux.org>
Felipe Lee <felipe.lee.garcia@gmail.com>
Filip Noetzel <http://filip.noetzel.co.uk/>
+ Filip Owczarek <f.a.owczarek@gmail.com>
Filip Wasilewski <filip.wasilewski@gmail.com>
Finn Gruwier Larsen <finn@gruwier.dk>
Fiza Ashraf <fizaashraf37@gmail.com>
diff --git a/django/db/backends/base/client.py b/django/db/backends/base/client.py
index 031056372d..1640bedcad 100644
--- a/django/db/backends/base/client.py
+++ b/django/db/backends/base/client.py
@@ -13,6 +13,9 @@ class BaseDatabaseClient:
# connection is an instance of BaseDatabaseWrapper.
self.connection = connection
+ def __del__(self):
+ del self.connection
+
@classmethod
def settings_to_cmd_args_env(cls, settings_dict, parameters):
raise NotImplementedError(
diff --git a/django/db/backends/base/creation.py b/django/db/backends/base/creation.py
index 845235bc72..1ed583f9e4 100644
--- a/django/db/backends/base/creation.py
+++ b/django/db/backends/base/creation.py
@@ -25,6 +25,9 @@ class BaseDatabaseCreation:
def __init__(self, connection):
self.connection = connection
+ def __del__(self):
+ del self.connection
+
def _nodb_cursor(self):
return self.connection._nodb_cursor()
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index 95230bb06b..6617894afa 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -407,6 +407,9 @@ class BaseDatabaseFeatures:
def __init__(self, connection):
self.connection = connection
+ def __del__(self):
+ del self.connection
+
@cached_property
def supports_explaining_query_execution(self):
"""Does this backend support explaining query execution?"""
diff --git a/django/db/backends/base/introspection.py b/django/db/backends/base/introspection.py
index 5e4acb3ff9..12360538b9 100644
--- a/django/db/backends/base/introspection.py
+++ b/django/db/backends/base/introspection.py
@@ -19,6 +19,9 @@ class BaseDatabaseIntrospection:
def __init__(self, connection):
self.connection = connection
+ def __del__(self):
+ del self.connection
+
def get_field_type(self, data_type, description):
"""
Hook for a database backend to use the cursor description to
diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py
index 5d1f260edf..fea73bc1e4 100644
--- a/django/db/backends/base/operations.py
+++ b/django/db/backends/base/operations.py
@@ -59,6 +59,9 @@ class BaseDatabaseOperations:
self.connection = connection
self._cache = None
+ def __del__(self):
+ del self.connection
+
def autoinc_sql(self, table, column):
"""
Return any SQL needed to support auto-incrementing primary keys, or
diff --git a/django/db/backends/base/validation.py b/django/db/backends/base/validation.py
index d0e3e2157d..b57b7dd16d 100644
--- a/django/db/backends/base/validation.py
+++ b/django/db/backends/base/validation.py
@@ -4,6 +4,9 @@ class BaseDatabaseValidation:
def __init__(self, connection):
self.connection = connection
+ def __del__(self):
+ del self.connection
+
def check(self, **kwargs):
return []
diff --git a/django/db/utils.py b/django/db/utils.py
index e45f1db249..faaf3bf862 100644
--- a/django/db/utils.py
+++ b/django/db/utils.py
@@ -64,6 +64,9 @@ class DatabaseErrorWrapper:
"""
self.wrapper = wrapper
+ def __del__(self):
+ del self.wrapper
+
def __enter__(self):
pass
diff --git a/tests/backends/base/test_base.py b/tests/backends/base/test_base.py
index 4418d010ea..8f47e30172 100644
--- a/tests/backends/base/test_base.py
+++ b/tests/backends/base/test_base.py
@@ -1,3 +1,4 @@
+import gc
from unittest.mock import MagicMock, patch
from django.db import DEFAULT_DB_ALIAS, connection, connections, transaction
@@ -60,6 +61,36 @@ class DatabaseWrapperTests(SimpleTestCase):
with patch.object(connection.features, "minimum_database_version", None):
connection.check_database_version_supported()
+ def test_release_memory_without_garbage_collection(self):
+ # Schedule the restore of the garbage collection settings.
+ self.addCleanup(gc.set_debug, 0)
+ self.addCleanup(gc.enable)
+
+ # Disable automatic garbage collection to control when it's triggered,
+ # then run a full collection cycle to ensure `gc.garbage` is empty.
+ gc.disable()
+ gc.collect()
+
+ # The garbage list isn't automatically populated to avoid CPU overhead,
+ # so debugging needs to be enabled to track all unreachable items and
+ # have them stored in `gc.garbage`.
+ gc.set_debug(gc.DEBUG_SAVEALL)
+
+ # Create a new connection that will be closed during the test, and also
+ # ensure that a `DatabaseErrorWrapper` is created for this connection.
+ test_connection = connection.copy()
+ with test_connection.wrap_database_errors:
+ self.assertEqual(test_connection.queries, [])
+
+ # Close the connection and remove references to it. This will mark all
+ # objects related to the connection as garbage to be collected.
+ test_connection.close()
+ test_connection = None
+
+ # Enforce garbage collection to populate `gc.garbage` for inspection.
+ gc.collect()
+ self.assertEqual(gc.garbage, [])
+
class DatabaseWrapperLoggingTests(TransactionTestCase):
available_apps = ["backends"]