Opened 14 years ago

Closed 14 years ago

Last modified 13 years ago

#12876 closed (fixed)

maximum recursion error when caching querysets on models

Reported by: zbyte64 Owned by: elachuni
Component: Database layer (models, ORM) Version: 1.2-beta
Severity: Keywords: pycamp2010
Cc: zbyte64@… Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

The following test case causes a runtime exception against django 1.2 beta 1:

from django.test import TestCase
from django.db import models

class TestModel(models.Model):
    def cachedmethod(self):
        if not hasattr(self, '_cachedmethod'):
            self._cachedmethod = RelatedModel.objects.all().filter(test_model=self)
        return self._cachedmethod

class RelatedModel(models.Model):
    test_model = models.ForeignKey(TestModel)
    category = models.CharField(max_length=50)

class dummy(object):
    def __init__(self, parent):
        self.parent = parent
    
    def cachedmethod(self):    
        if not hasattr(self, '_cachedmethod'):
            self._cachedmethod = RelatedModel.objects.all().filter(test_model=self.parent)
        return self._cachedmethod

class BreakMeTest(TestCase):
    def test_caching(self):
        parent = TestModel()
        parent.save()
        related = RelatedModel(category='foo', test_model=parent)
        related.save()
        
        adummy = dummy(parent)
        adummy.cachedmethod()
        adummy.cachedmethod().filter(category='foo') #this doesn't break it
        parent.cachedmethod()
        parent.cachedmethod().filter(category='foo') #this breaks it

Attachments (2)

results.txt (99.5 KB ) - added by zbyte64 14 years ago.
Stack trace of exception
bug12876.patch (2.9 KB ) - added by elachuni 14 years ago.
Patch with a brief doctest

Download all attachments as: .zip

Change History (11)

by zbyte64, 14 years ago

Attachment: results.txt added

Stack trace of exception

comment:1 by Russell Keith-Magee, 14 years ago

milestone: 1.2
Triage Stage: UnreviewedAccepted

comment:2 by Russell Keith-Magee, 14 years ago

Component: UncategorizedDatabase layer (models, ORM)

comment:3 by elachuni, 14 years ago

Owner: changed from nobody to elachuni

django.db.models.sql.Query defines a custom __deepcopy__ that calls self.clone(), disregarding the provided memo dict.

So, if the query contains a reference to a model instance that in turn contains a reference to a reference (as in this case), deepcopy will end up in an endless loop.

The model itself could work around this problem by providing its own __deepcopy__ implementation, that just didn't include the query. For the example in the bugreport:

class TestModel(models.Model):
    def __deepcopy__(self, memo):
        return TestModel(id=self.id)
    ....

comment:4 by Ramiro Morales, 14 years ago

Has patch: set
Keywords: pycamp2010 added

in reply to:  3 comment:5 by elachuni, 14 years ago

The attached patch adds an optional argument 'memo' to clone, to be able to correctly implement deepcopy.

Replying to elachuni:

So, if the query contains a reference to a model instance that in turn contains a reference to a reference (...)

Should have been a reference to a query here.

by elachuni, 14 years ago

Attachment: bug12876.patch added

Patch with a brief doctest

comment:6 by Alex Gaynor, 14 years ago

Triage Stage: AcceptedReady for checkin

comment:7 by Russell Keith-Magee, 14 years ago

Resolution: fixed
Status: newclosed

(In [12700]) Fixed #12876 -- Corrected a problem with recursive relations under deepcopy. Thanks to elachuni for the patch.

comment:8 by Russell Keith-Magee, 14 years ago

(In [12702]) [1.1.X] Fixed #12876 -- Corrected a problem with recursive relations under deepcopy. Thanks to elachuni for the patch.

Backport of r12700 from trunk.

comment:9 by Jacob, 13 years ago

milestone: 1.2

Milestone 1.2 deleted

Note: See TracTickets for help on using tickets.
Back to Top