[PEAK] Recursive __setstate__ call

Phillip J. Eby pje at telecommunity.com
Thu Mar 25 11:52:02 EST 2004


It looks to me like setting an attribute on a ghost is somehow causing 
this, which means it's probably in the ZODB4 persistence code.  If you look 
at the traceback, you'll see that the first call can't possibly be calling 
the second.  In fact, if you look around the Python code, you'll see that 
nothing in PEAK even calls the 'setstate()' method of a DM, that's a hook 
that's called from the C persistence code.  It looks to me like the pattern 
is this:

1. your code does a setattr on the ghost, so persistence machinery calls 
setstate()

2. setstate() refers to __setstate__, which is a getattr on the ghost

3. apparently, the ghost thinks it should call setstate() again in order to 
deghostify.

So, it's almost certainly something wrong with the state management logic 
in _persistence.c.  It might be possible to workaround by having 
'setstate()' force ob._p_changed to a particular state before calling 
__setstate__, but I'm not sure.


At 05:30 PM 3/25/04 +0100, Radek Kanovsky wrote:
>Hi all,
>
>there is probably something wrong with storage.DM or model.Element.
>I have traced SQL queries in my application and noticed that `_load'
>method is called many time twice at one point. Bellow is minimal example
>that does the same. I don't know yet what's wrong but origin is in
>recursive call of setstate method of EntityDM. The effect would be seen
>if `raise ...' command in _load method is uncommented.
>
>     from peak.api import *
>
>     class User(model.Element):
>         class uid (model.Attribute):
>             referencedType = model.Integer
>         class login (model.Attribute):
>             referencedType = model.String
>
>     class UserDM(storage.EntityDM):
>         db           = binding.Obtain("pgsql://me:mypwd@localhost/test")
>         defaultClass = User
>         def _load(self, oid, ob):
>             #raise Exception(oid)
>             row = ~self.db('SELECT * FROM "user" WHERE uid=%s' % oid)
>             return self.stateFromRow(row)
>         def stateFromRow(self,row):
>             return {"uid":row.uid, "login":row.login}
>         def _save(self, ob):
>             self.db("""UPDATE "user" SET uid=%d, login='%s' WHERE 
> oid=%d""" % (
>                        ob.uid, ob.login, ob._p_oid))
>
>     dm = UserDM(config.makeRoot())
>
>     storage.begin(dm)
>     user = dm[1]
>     user.login = "hello"
>     storage.commit(dm)
>
>
>
>Here is traceback seen from _load method that shows setstate
>recursive call:
>
>     Traceback (most recent call last):
>       File "./userDM.py", line 28, in ?
>         user.login = "hello"
>       File "site-packages/peak/storage/data_managers.py", line 318, in 
> setstate
>         ob.__setstate__(self._load(oid,ob))
>       File "site-packages/peak/storage/data_managers.py", line 318, in 
> setstate
>         ob.__setstate__(self._load(oid,ob))
>       File "./userDM.py", line 14, in _load
>         raise Exception(oid)
>     Exception: 1
>
>
>And here is SQL log if raise is commented:
>
>     SELECT * FROM "user" WHERE uid=1
>     SELECT * FROM "user" WHERE uid=1
>     UPDATE "user" SET uid=1, login='hello' WHERE oid=1
>
>
>If I exceute
>
>     storage.begin(dm)
>     user = dm[1]
>     print user.login
>     storage.commit(dm)
>
>_load method is called only once.
>I use python 2.3.3.
>
>RadekK
>_______________________________________________
>PEAK mailing list
>PEAK at eby-sarna.com
>http://www.eby-sarna.com/mailman/listinfo/peak




More information about the PEAK mailing list