#36674: select_related() leaks memory
-------------------------------------+-------------------------------------
Reporter: Jacob Walls | Type:
| Cleanup/optimization
Status: new | Component: Database
| layer (models, ORM)
Version: dev | Severity: Normal
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Calls to `select_related()` create nested functions that aren't cleaned up
until garbage collection runs:
Borrowed testing strategy from #34865:
{{{#!diff
diff --git a/tests/select_related/tests.py b/tests/select_related/tests.py
index 41ed350cf3..252e884d7a 100644
--- a/tests/select_related/tests.py
+++ b/tests/select_related/tests.py
@@ -242,6 +242,30 @@ class SelectRelatedTests(TestCase):
FETCH_PEERS,
)
+ def test_memory_leak(self):
+ # todo: factor out a test util
+ import gc
+
+ # 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)
+
+ list(Species.objects.select_related("genus"))
+
+ # Enforce garbage collection to populate `gc.garbage` for
inspection.
+ gc.collect()
+ self.assertEqual(gc.garbage, [])
+
class SelectRelatedValidationTests(SimpleTestCase):
"""
}}}
Gives:
{{{#!diff
AssertionError: Lists differ: [<cell at 0x10630c940: function object at
[148 chars]040>] != []
First list contains 3 additional elements.
First extra element 0:
<cell at 0x10630c940: function object at 0x1079b8040>
+ []
- [<cell at 0x10630c940: function object at 0x1079b8040>,
- (<cell at 0x10630c940: function object at 0x1079b8040>,),
- <function SQLCompiler.get_select.<locals>.get_select_from_parent at
0x1079b8040>]
}}}
At a glance, it looks like we can lift `get_select_from_parent` to a class
method or all the way to module-level without a loss of functionality to
address this.
--
Ticket URL: <
https://code.djangoproject.com/ticket/36674>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.