Commit 03c08efa authored by Jim Fulton's avatar Jim Fulton

Merged conflict-resolution bug fixes from trunk:

r77078 | gary | 2007-06-25 16:39:45 -0400 (Mon, 25 Jun 2007) | 1 line
r77010 | gary | 2007-06-24 13:19:18 -0400 (Sun, 24 Jun 2007) | 2 lines
r76977 | gary | 2007-06-23 06:57:51 -0400 (Sat, 23 Jun 2007) | 1 line
r76953 | gary | 2007-06-23 00:20:55 -0400 (Sat, 23 Jun 2007) | 1 line

Document conflict resolution. bugfix the situation in which comparing
persistent objects (for instance, as members in BTree set or keys of
BTree) might cause data inconsistency during conflict resolution.
support multidatabase references in conflict resolution.  make it
possible to examine oid and (in some situations) database name of
persistent object references so that I can add some code to
zope.app.keyreference to support these objects (so BTree conflict
resolution can happen at all when keyreferences are used).
parent 3d523683
...@@ -14,6 +14,17 @@ General ...@@ -14,6 +14,17 @@ General
apply the garbage collections on those connections in the pool that are apply the garbage collections on those connections in the pool that are
closed. (This fixed issue 113932.) closed. (This fixed issue 113932.)
- (3.8.0b3) Document conflict resolution (see ZODB/ConflictResolution.txt).
- (3.8.0b3) Bugfix the situation in which comparing persistent objects (for
instance, as members in BTree set or keys of BTree) might cause data
inconsistency during conflict resolution.
- (3.8.0b3) Support multidatabase references in conflict resolution.
- (3.8.0b3) Make it possible to examine oid and (in some situations) database
name of persistent object references during conflict resolution.
ZEO ZEO
--- ---
......
...@@ -17,6 +17,8 @@ from cStringIO import StringIO ...@@ -17,6 +17,8 @@ from cStringIO import StringIO
from cPickle import Unpickler, Pickler from cPickle import Unpickler, Pickler
from pickle import PicklingError from pickle import PicklingError
import zope.interface
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from ZODB.loglevels import BLATHER from ZODB.loglevels import BLATHER
...@@ -58,7 +60,87 @@ def state(self, oid, serial, prfactory, p=''): ...@@ -58,7 +60,87 @@ def state(self, oid, serial, prfactory, p=''):
unpickler.load() # skip the class tuple unpickler.load() # skip the class tuple
return unpickler.load() return unpickler.load()
class PersistentReference: class IPersistentReference(zope.interface.Interface):
'''public contract for references to persistent objects from an object
with conflicts.'''
oid = zope.interface.Attribute(
'The oid of the persistent object that this reference represents')
database_name = zope.interface.Attribute(
'''The name of the database of the reference, *if* different.
If not different, None.''')
klass = zope.interface.Attribute(
'''class meta data. Presence is not reliable.''')
weak = zope.interface.Attribute(
'''bool: whether this reference is weak''')
def __cmp__(other):
'''if other is equivalent reference, return 0; else raise ValueError.
Equivalent in this case means that oid and database_name are the same.
If either is a weak reference, we only support `is` equivalence, and
otherwise raise a ValueError even if the datbase_names and oids are
the same, rather than guess at the correct semantics.
It is impossible to sort reliably, since the actual persistent
class may have its own comparison, and we have no idea what it is.
We assert that it is reasonably safe to assume that an object is
equivalent to itself, but that's as much as we can say.
We don't compare on 'is other', despite the
PersistentReferenceFactory.data cache, because it is possible to
have two references to the same object that are spelled with different
data (for instance, one with a class and one without).'''
class PersistentReference(object):
zope.interface.implements(IPersistentReference)
weak = False
oid = database_name = klass = None
def __init__(self, data):
self.data = data
# see serialize.py, ObjectReader._persistent_load
if isinstance(data, tuple):
self.oid, self.klass = data
elif isinstance(data, str):
self.oid = data
else: # a list
reference_type = data[0]
# 'm' = multi_persistent: (database_name, oid, klass)
# 'n' = multi_oid: (database_name, oid)
# 'w' = persistent weakref: (oid)
# else it is a weakref: reference_type
if reference_type == 'm':
self.database_name, self.oid, self.klass = data[1]
elif reference_type == 'n':
self.database_name, self.oid = data[1]
elif reference_type == 'w':
self.oid, = data[1]
self.weak = True
else:
assert len(data) == 1, 'unknown reference format'
self.oid = data[0]
self.weak = True
def __cmp__(self, other):
if self is other or (
isinstance(other, PersistentReference) and
self.oid == other.oid and
self.database_name == other.database_name and
not self.weak and
not other.weak):
return 0
else:
raise ValueError(
"can't reliably compare against different "
"PersistentReferences")
def __repr__(self): def __repr__(self):
return "PR(%s %s)" % (id(self), self.data) return "PR(%s %s)" % (id(self), self.data)
...@@ -70,15 +152,15 @@ class PersistentReferenceFactory: ...@@ -70,15 +152,15 @@ class PersistentReferenceFactory:
data = None data = None
def persistent_load(self, oid): def persistent_load(self, ref):
if self.data is None: if self.data is None:
self.data = {} self.data = {}
key = tuple(ref) # lists are not hashable; formats are different enough
r = self.data.get(oid, None) # even after eliminating list/tuple distinction
r = self.data.get(key, None)
if r is None: if r is None:
r = PersistentReference() r = PersistentReference(ref)
r.data = oid self.data[key] = r
self.data[oid] = r
return r return r
......
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2007 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""
$Id$
"""
import unittest
from zope.testing import doctest, module
def setUp(test):
module.setUp(test, 'ConflictResolution_txt')
def tearDown(test):
test.globs['db'].close()
test.globs['db1'].close()
test.globs['db2'].close()
module.tearDown(test)
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('../ConflictResolution.txt',
setUp=setUp,
tearDown=tearDown,
optionflags=doctest.INTERPRET_FOOTNOTES,
),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
...@@ -25,11 +25,61 @@ import time ...@@ -25,11 +25,61 @@ import time
import persistent import persistent
import transaction import transaction
from ZODB.MappingStorage import MappingStorage from ZODB.MappingStorage import MappingStorage
from ZODB.ConflictResolution import ConflictResolvingStorage
from ZODB.DB import DB as _DB from ZODB.DB import DB as _DB
from ZODB import POSException
def DB(name='Test', **dbargs): def DB(name='Test', **dbargs):
return _DB(MappingStorage(name), **dbargs) return _DB(MappingStorage(name), **dbargs)
class ConflictResolvingMappingStorage(
MappingStorage, ConflictResolvingStorage):
def __init__(self, name='ConflictResolvingMappingStorage'):
MappingStorage.__init__(self, name)
self._old = {}
def loadSerial(self, oid, serial):
self._lock_acquire()
try:
old_info = self._old[oid]
try:
return old_info[serial]
except KeyError:
raise POSException.POSKeyError(oid)
finally:
self._lock_release()
def store(self, oid, serial, data, version, transaction):
if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction)
if version:
raise POSException.Unsupported("Versions aren't supported")
self._lock_acquire()
try:
if oid in self._index:
oserial = self._index[oid][:8]
if serial != oserial:
rdata = self.tryToResolveConflict(
oid, oserial, serial, data)
if rdata is None:
raise POSException.ConflictError(
oid=oid, serials=(oserial, serial), data=data)
else:
data = rdata
self._tindex[oid] = self._tid + data
finally:
self._lock_release()
return self._tid
def _finish(self, tid, user, desc, ext):
self._index.update(self._tindex)
self._ltid = self._tid
for oid, record in self._tindex.items():
self._old.setdefault(oid, {})[self._tid] = record[8:]
def commit(): def commit():
transaction.commit() transaction.commit()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment