1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

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.
This commit is contained in:
fowczrek 2025-02-08 14:19:58 +01:00 committed by nessita
parent e804a07d76
commit 6a9db1e626
9 changed files with 53 additions and 0 deletions

View File

@ -349,6 +349,7 @@ answer newbie questions, and generally made Django that much better:
Federico Capoano <nemesis@ninux.org> Federico Capoano <nemesis@ninux.org>
Felipe Lee <felipe.lee.garcia@gmail.com> Felipe Lee <felipe.lee.garcia@gmail.com>
Filip Noetzel <http://filip.noetzel.co.uk/> Filip Noetzel <http://filip.noetzel.co.uk/>
Filip Owczarek <f.a.owczarek@gmail.com>
Filip Wasilewski <filip.wasilewski@gmail.com> Filip Wasilewski <filip.wasilewski@gmail.com>
Finn Gruwier Larsen <finn@gruwier.dk> Finn Gruwier Larsen <finn@gruwier.dk>
Fiza Ashraf <fizaashraf37@gmail.com> Fiza Ashraf <fizaashraf37@gmail.com>

View File

@ -13,6 +13,9 @@ class BaseDatabaseClient:
# connection is an instance of BaseDatabaseWrapper. # connection is an instance of BaseDatabaseWrapper.
self.connection = connection self.connection = connection
def __del__(self):
del self.connection
@classmethod @classmethod
def settings_to_cmd_args_env(cls, settings_dict, parameters): def settings_to_cmd_args_env(cls, settings_dict, parameters):
raise NotImplementedError( raise NotImplementedError(

View File

@ -25,6 +25,9 @@ class BaseDatabaseCreation:
def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection
def __del__(self):
del self.connection
def _nodb_cursor(self): def _nodb_cursor(self):
return self.connection._nodb_cursor() return self.connection._nodb_cursor()

View File

@ -407,6 +407,9 @@ class BaseDatabaseFeatures:
def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection
def __del__(self):
del self.connection
@cached_property @cached_property
def supports_explaining_query_execution(self): def supports_explaining_query_execution(self):
"""Does this backend support explaining query execution?""" """Does this backend support explaining query execution?"""

View File

@ -19,6 +19,9 @@ class BaseDatabaseIntrospection:
def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection
def __del__(self):
del self.connection
def get_field_type(self, data_type, description): def get_field_type(self, data_type, description):
""" """
Hook for a database backend to use the cursor description to Hook for a database backend to use the cursor description to

View File

@ -59,6 +59,9 @@ class BaseDatabaseOperations:
self.connection = connection self.connection = connection
self._cache = None self._cache = None
def __del__(self):
del self.connection
def autoinc_sql(self, table, column): def autoinc_sql(self, table, column):
""" """
Return any SQL needed to support auto-incrementing primary keys, or Return any SQL needed to support auto-incrementing primary keys, or

View File

@ -4,6 +4,9 @@ class BaseDatabaseValidation:
def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection
def __del__(self):
del self.connection
def check(self, **kwargs): def check(self, **kwargs):
return [] return []

View File

@ -64,6 +64,9 @@ class DatabaseErrorWrapper:
""" """
self.wrapper = wrapper self.wrapper = wrapper
def __del__(self):
del self.wrapper
def __enter__(self): def __enter__(self):
pass pass

View File

@ -1,3 +1,4 @@
import gc
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from django.db import DEFAULT_DB_ALIAS, connection, connections, transaction 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): with patch.object(connection.features, "minimum_database_version", None):
connection.check_database_version_supported() 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): class DatabaseWrapperLoggingTests(TransactionTestCase):
available_apps = ["backends"] available_apps = ["backends"]