[ZODB-Dev] Tracking down a freeze (deadlock?)

Tim Peters tim at zope.com
Mon Mar 7 17:01:26 EST 2005


[Tim]
>> ...
>> Or are "the rules of persistence" documented in some place other than
>> [the ZODB/ZEO Programming Guide] (if so, where?)?

[Jim]
> I have no idea.

In that case, updating the rules of persistence is very easy -- there's
nothing to be changed <wink>.

Googling on "rules of persistence" (including the quotes!) turns up lots of
hits, all seeming to originate with your ZODB presentation in 2000, which
has a section title with that name:

http://www.python.org/workshops/2000-01/proceedings/papers/fulton/zodb3.html

AFAIK, that wasn't updated, and the source for it wasn't checked in.  Right?
IOW, the only user-level doc I know of for ZODB that ever gets updated is
the ZODB/ZEO Programming Guide (which is checked in to the project, and gets
minimal attention from time to time).

> ...
> In earlier messages, It looked like someone tracked down the deadlock to
> a __del__ causing an object to get loaded when an object was invalidated.

Florent showed a traceback of that form, yes.

> (An object was invalidated, which caused another object's refcount to
> fall to 0, which caused it's __del__ to be called.  The __del__ caused
> some object to be reloaded, which called setstate on the connection.  The
> setstate tried to get the invalidation lock, which was already held by
> the code doing the invalidation.)

That's a nastier problem than we've been discussing here, right?  Meaning
that there's nothing special about a __del__ method attached to a
*persistent* class in this scenario -- any object (persistent or not) with a
__del__ method could trigger a deadlock ... like so:

"""
import ZODB
from ZODB.FileStorage import FileStorage
from Persistence import Persistent

st = FileStorage('temp.fs')
db = ZODB.DB(st)
cn = db.open()
rt = cn.root()

class C(Persistent):
    def __init__(self, attr=None):
        self.attr = attr

class D:
    def __del__(self):
        print 'in __del__'
        print cn.root()['c2'].attr

rt['c1'] = C(D())
rt['c2'] = C("success")
get_transaction().commit()

cn.invalidate({rt['c1']._p_oid: 1, rt['c2']._p_oid: 1})
print "flushing"
cn._flush_invalidations()
print "done flushing"
"""

That prints "in __del__" as a side effect of calling _flush_invalidations(),
then deadlocks on the next line.  Hmm!  Can't account for this:  sometimes
it *doesn't* deadlock, completing with "success".  Yikes.

OK, that depends on the hash codes of the oids <heh>.  Reliable deadlock:

"""
import ZODB
from ZODB.FileStorage import FileStorage
from Persistence import Persistent

st = FileStorage('temp.fs')
db = ZODB.DB(st)
cn = db.open()
rt = cn.root()

class C(Persistent):
    def __init__(self, attr=None):
        self.attr = attr

class D:
    def __del__(self):
        print 'in __del__'
        print cn.root()['c2'].attr

rt['c1'] = C(D())
rt['c2'] = C("success")
get_transaction().commit()

cn.invalidate({rt['c2']._p_oid: 1})
print "flushing 1"
cn._flush_invalidations()
cn.invalidate({rt['c1']._p_oid: 1})
print "flushing 2"
cn._flush_invalidations()
print "done flushing"
"""

> This is very similar to the problem I ran into with ZClasses. When a
> ZClass is invalidated, we want to reload the state.  Originally, this
> lead to a deadlock.  I had to change some the invalidation strategy so
> that the code the code that performes invalidations doesn't hold the
> invalidation lock.

I'd much rather spend time on that than trying to defang hostile __del__
methods.



More information about the ZODB-Dev mailing list