[Django] #36701: ModelState objects create reference cycles that require a gc pass to free

15 views
Skip to first unread message

Django

unread,
Oct 31, 2025, 10:25:57 AMOct 31
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Type: Bug
Status: new | Component: Database
| layer (models, ORM)
Version: 5.2 | Severity: Normal
Keywords: memory gc | Triage Stage:
modelstate fields_cache | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Disclaimer: it's impossible for pure Python code to truly leak memory (in
the sense that valgrind would detect), however it's quite easy to create
structures that effectively occupy memory for a long time because they
require the deepest (generation 2) garbage collection cycle to collect and
that happens very rarely. In addition to that, the more such structures
aggregate, the more expensive the garbage collection cycle becomes,
because it effectively stops the entire interpreter to do its job and it
can take seconds. On top of that, it's entirely possible for a container
to run out of memory before the garbage collection happens and we (Saleor
Commerce) see containers being terminated by the kernel OOM killer due to
high memory pressure where most of that memory is locked by garbage.

Each model instance includes a `ModelState` object that in turn contains
references to other model instances. It's possible for those to be cyclic.
In fact, the simplest cyclic case is a `OneToOneField` that links the
states of both objects to the opposite side as soon as the relationship is
traversed.

1. When `foo.bar` is evaluated, the `ForwardManyToOneDescriptor` fetches
the related `Bar` object and sets `foo._state.fields_cache["bar"]` to the
retrieved instance (through a call to `Field.set_cached_value`).
2. If the relation is not `multiple` (so a one-to-one), it also sets
`bar._state.fields_cache["foo"]` to the `foo` object on the `Bar`
instance.

While `OneToOneField` is the easiest way to create such a cycle, it's also
possible to create them through manual assignment (`foo.bar = bar`,
`bar.baz = baz`, `baz.foo = foo`), although one might argue that a
manually created cycle is the fault and therefore concern of the user.

The easiest way to solve this is to break the reference cycle by
implementing a finalizer (`__del__` method) on either the base `Model`
class to remove the state (`del self._state`), or the `ModelState` class
to remove the field cache (`self.fields_cache.clear()`).
--
Ticket URL: <https://code.djangoproject.com/ticket/36701>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Oct 31, 2025, 10:26:55 AMOct 31
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner: (none)
Type: Bug | Status: new
Component: Database layer | Version: 5.2
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage:
modelstate fields_cache | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Patryk Zawadzki):

* Attachment "image-20251031-152647.png" added.

Django

unread,
Oct 31, 2025, 3:28:52 PMOct 31
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage: Accepted
modelstate fields_cache |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* stage: Unreviewed => Accepted
* type: Bug => Cleanup/optimization
* version: 5.2 => dev

Comment:

Hi Patryk, thanks for the careful report.
[https://docs.python.org/3/whatsnew/3.14.html#incremental-garbage-
collection Incremental garbage collection] is new in Python 3.14 and might
make most of your headaches here go away:

> This means that maximum pause times are reduced by an order of magnitude
or more for larger heaps.
> There are now only two generations: young and old.

The behavior difference between foreign keys and one-to-one fields here is
surprising, though. Tentatively accepting so we can look at a patch.
Thanks.
--
Ticket URL: <https://code.djangoproject.com/ticket/36701#comment:1>

Django

unread,
Nov 1, 2025, 7:22:55 AMNov 1
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner: Varun
Type: | Kasyap Pentamaraju
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage: Accepted
modelstate fields_cache |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Varun Kasyap Pentamaraju):

* owner: (none) => Varun Kasyap Pentamaraju
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/36701#comment:2>

Django

unread,
Nov 1, 2025, 10:06:37 AMNov 1
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner: Varun
Type: | Kasyap Pentamaraju
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage: Accepted
modelstate fields_cache |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Varun Kasyap Pentamaraju):

Hi,

I tried adding the `__del__` method to `ModelState` as suggested.

I also wrote a `gc` test to check for the `OneToOneField` reference cycle.
The test is still **failing**, and the objects are not being collected.

I am worried that adding `__del__` to an object inside a reference cycle
might be preventing the garbage collector from cleaning it up. This
`__del__` approach might not be the right fix here.
--
Ticket URL: <https://code.djangoproject.com/ticket/36701#comment:3>

Django

unread,
Nov 3, 2025, 11:47:39 AMNov 3
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner: Varun
Type: | Kasyap Pentamaraju
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage: Accepted
modelstate fields_cache |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Patryk Zawadzki):

Hey, we have a working internal workaround that adds `__del__` to model
classes and just does `del self._state` and it seems to work great.
--
Ticket URL: <https://code.djangoproject.com/ticket/36701#comment:4>

Django

unread,
Nov 6, 2025, 7:52:04 AMNov 6
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner: (none)
Type: | Status: new
Cleanup/optimization |
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage: Accepted
modelstate fields_cache |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Varun Kasyap Pentamaraju):

* owner: Varun Kasyap Pentamaraju => (none)
* status: assigned => new

--
Ticket URL: <https://code.djangoproject.com/ticket/36701#comment:5>

Django

unread,
Nov 19, 2025, 12:11:34 AMNov 19
to django-...@googlegroups.com
#36701: ModelState objects create reference cycles that require a gc pass to free
-------------------------------------+-------------------------------------
Reporter: Patryk Zawadzki | Owner:
Type: | GoMyeongju
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: memory gc | Triage Stage: Accepted
modelstate fields_cache |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by GoMyeongju):

* owner: (none) => GoMyeongju
* status: new => assigned

--
Ticket URL: <https://code.djangoproject.com/ticket/36701#comment:6>
Reply all
Reply to author
Forward
0 new messages