[ZODB-Dev] ZODB4 - Why two inheritance branches for ConflictError ?

Ury Marshak um@hottech-israel.com
Wed, 3 Jul 2002 12:15:13 +0200


ZODB4 - Why two inheritance branches for ConflictError ?

While digging around in ZODB4 I accidently discovered that
the class BTreesConflictError (in Persistence/BTrees/Exception.py)
inherits from Transaction.Exceptions.ConflictError . What
happens is that the code (including the ZODB itself) using the 
'from ZODB.POSException import ConflictError' line is unable to
catch the Btrees exceptions, cause ZODB.POSException.ConflictError
is also inheriting from Transaction.Exceptions.ConflictError. Is this
the new inheritance model (meaning, do I have to catch exceptions
by using 'except Transaction.Exceptions.ConflictError:' now?

On an unrelated note.. I still have trouble using ZEO with ZODB4,
although it's not related to BTrees, rather to ZEO - ZODB interaction.
I've tracked it down to function in ZEO/StorageServer.py :

class ZEOStorage:
    .........
    def zeoLoad(self, oid):

        v = self.__storage.modifiedInVersion(oid)

the call to modifiedInVersion throws a KeyError. Since I've no idea
how the ZEO functions, can someone more knowledgeable explain if this
is supposed to happen (we are trying to load an object that doesn't
exist, right?) This happens when we try to insert a new object in a
collection (I tried using BTree and PersistentList) and have a conflict
and when we retry the insert (BTW is it ok that after a failed insert
and a ConflictError exception there is a not-None ._p_oid attribute
on the new object? It seems that it is that oid that bobms out on a
retry...). 
Well, this is a real show-stopper to me, cause running multiuser is
an absolute must.. I've tried to cut the example code down, it's
attached below:

------------- file OList.py -----------------
import ZODB
import Persistence
from Persistence.BTrees.IOBTree import IOBTree
#from ZODB.POSException import ConflictError
from Transaction.Exceptions import ConflictError

class OList(Persistence.Persistent):
    def __init__(self):
        self._tree = IOBTree()
        self._next_id = 1

    def add_new(self, obj):
        """Adds new object, returns id"""
        while 1:
            try:
                new_id = self._next_id
                self._next_id +=1
                self._tree[new_id] = obj
                print 'new id: ',new_id   
                get_transaction().commit()
            except ConflictError:
                print 'conflict' # here we have obj._p_oid. is it right?
            else:
                break
        return new_id

------------- file test2.py -----------------

from ZEO import ClientStorage
from ZODB import DB
import ZODB
import Persistence
from Persistence.BTrees.IOBTree import IOBTree

#from ZODB.POSException import ConflictError
from Transaction.Exceptions import ConflictError

from OList import OList

class C(Persistence.Persistent): pass

treename = 'aaaa'

def gen_conflict(db):
    conn = db.open()
    root = conn.root()
    obj_tree = root[treename]
    c_id = obj_tree.add_new( C() )
    print 'added number ', c_id

if __name__ == "__main__":
    storage = ClientStorage.ClientStorage(('127.0.0.1',7980))
    db = DB( storage )
    conn = db.open()
    root = conn.root()


    if not root.has_key(treename):
        root[treename]=OList()
        get_transaction().commit()

    obj_tree = root[treename]
    print id(obj_tree)

    c_id = obj_tree.add_new( C() )
    print 'added number ', c_id

    gen_conflict(db)

    c_id = obj_tree.add_new( C() )
    print 'added number ', c_id


-----------------------------------------------------------

Also a small diff for the ZODB/ConflictResolution.py;
without it the error is masked by an ImportError.

*** ZODB/ConflictResolution.py Tue Jun 11 02:27:44 2002
--- fix/ConflictResolution.py Wed Jul  3 12:01:07 2002
***************
*** 85,91 ****
              return 0
  
          newstate = unpickler.load()
!         klass = _classFactory(class_tuple[0], class_tuple[1])
          inst = klass.__new__(klass)
  
          try:
--- 85,95 ----
              return 0
  
          newstate = unpickler.load()
!         try:
!             klass = _classFactory(class_tuple[0], class_tuple[1])
!         except ImportError:
!             bad_classes[class_tuple]=1
!             return 0
          inst = klass.__new__(klass)
  
          try: