[Zodb-checkins] SVN: ZODB/trunk/src/ZODB/ Removed the mvcc option.
Everybody wants mvcc and removing us lets us
Jim Fulton
jim at zope.com
Sat May 5 14:32:17 EDT 2007
Log message for revision 75512:
Removed the mvcc option. Everybody wants mvcc and removing us lets us
simplify the code a little. (We'll be able to simplify more when we
stop supporting versions.)
Suppress version-deprecation warnings for tests.
Changed:
U ZODB/trunk/src/ZODB/Connection.py
U ZODB/trunk/src/ZODB/DB.py
U ZODB/trunk/src/ZODB/tests/testZODB.py
-=-
Modified: ZODB/trunk/src/ZODB/Connection.py
===================================================================
--- ZODB/trunk/src/ZODB/Connection.py 2007-05-05 18:32:12 UTC (rev 75511)
+++ ZODB/trunk/src/ZODB/Connection.py 2007-05-05 18:32:16 UTC (rev 75512)
@@ -89,7 +89,6 @@
self.connections = {self._db.database_name: self}
self._synch = None
- self._mvcc = None
self._version = version
self._normal_storage = self._storage = db._storage
@@ -348,7 +347,7 @@
if connection is None:
new_con = self._db.databases[database_name].open(
transaction_manager=self.transaction_manager,
- mvcc=self._mvcc, version=self._version, synch=self._synch,
+ version=self._version, synch=self._synch,
)
self.connections.update(new_con.connections)
new_con.connections = self.connections
@@ -871,7 +870,7 @@
def _load_before_or_conflict(self, obj):
"""Load non-current state for obj or raise ReadConflictError."""
- if not (self._mvcc and self._setstate_noncurrent(obj)):
+ if not ((not self._version) and self._setstate_noncurrent(obj)):
self._register(obj)
self._conflicts[obj._p_oid] = True
raise ReadConflictError(object=obj)
@@ -971,8 +970,7 @@
# return a list of [ghosts....not recently used.....recently used]
return everything.items() + items
- def open(self, transaction_manager=None, mvcc=True, synch=True,
- delegate=True):
+ def open(self, transaction_manager=None, synch=True, delegate=True):
"""Register odb, the DB that this Connection uses.
This method is called by the DB every time a Connection
@@ -984,13 +982,11 @@
Parameters:
odb: database that owns the Connection
- mvcc: boolean indicating whether MVCC is enabled
transaction_manager: transaction manager to use. None means
use the default transaction manager.
synch: boolean indicating whether Connection should
register for afterCompletion() calls.
"""
-
# TODO: Why do we go to all the trouble of setting _db and
# other attributes on open and clearing them on close?
# A Connection is only ever associated with a single DB
@@ -998,7 +994,6 @@
self._opened = time()
self._synch = synch
- self._mvcc = mvcc and not self._version
if transaction_manager is None:
transaction_manager = transaction.manager
@@ -1021,7 +1016,7 @@
# delegate open to secondary connections
for connection in self.connections.values():
if connection is not self:
- connection.open(transaction_manager, mvcc, synch, False)
+ connection.open(transaction_manager, synch, False)
def _resetCache(self):
"""Creates a new cache, discarding the old one.
Modified: ZODB/trunk/src/ZODB/DB.py
===================================================================
--- ZODB/trunk/src/ZODB/DB.py 2007-05-05 18:32:12 UTC (rev 75511)
+++ ZODB/trunk/src/ZODB/DB.py 2007-05-05 18:32:16 UTC (rev 75512)
@@ -554,8 +554,7 @@
def objectCount(self):
return len(self._storage)
- def open(self, version='', mvcc=True,
- transaction_manager=None, synch=True):
+ def open(self, version='', transaction_manager=None, synch=True):
"""Return a database Connection for use by application code.
The optional `version` argument can be used to specify that a
@@ -568,7 +567,6 @@
:Parameters:
- `version`: the "version" that all changes will be made
in, defaults to no version.
- - `mvcc`: boolean indicating whether MVCC is enabled
- `transaction_manager`: transaction manager to use. None means
use the default transaction manager.
- `synch`: boolean indicating whether Connection should
@@ -609,7 +607,7 @@
assert result is not None
# Tell the connection it belongs to self.
- result.open(transaction_manager, mvcc, synch)
+ result.open(transaction_manager, synch)
# A good time to do some cache cleanup.
self._connectionMap(lambda c: c.cacheGC())
Modified: ZODB/trunk/src/ZODB/tests/testZODB.py
===================================================================
--- ZODB/trunk/src/ZODB/tests/testZODB.py 2007-05-05 18:32:12 UTC (rev 75511)
+++ ZODB/trunk/src/ZODB/tests/testZODB.py 2007-05-05 18:32:16 UTC (rev 75512)
@@ -16,6 +16,7 @@
import ZODB
import ZODB.FileStorage
+import ZODB.MappingStorage
from ZODB.POSException import ReadConflictError, ConflictError
from ZODB.POSException import TransactionFailedError
from ZODB.tests.warnhook import WarningsHook
@@ -29,6 +30,9 @@
warnings.filterwarnings("ignore",
".*\nsubtransactions are deprecated",
DeprecationWarning, __name__)
+warnings.filterwarnings("ignore",
+ "Versions are deprecated",
+ DeprecationWarning, __name__)
class P(Persistent):
pass
@@ -213,114 +217,6 @@
conn1.close()
conn2.close()
- def checkReadConflict(self):
- self.obj = P()
- self.readConflict()
-
- def readConflict(self, shouldFail=True):
- # Two transactions run concurrently. Each reads some object,
- # then one commits and the other tries to read an object
- # modified by the first. This read should fail with a conflict
- # error because the object state read is not necessarily
- # consistent with the objects read earlier in the transaction.
-
- tm1 = transaction.TransactionManager()
- conn = self._db.open(mvcc=False, transaction_manager=tm1)
- r1 = conn.root()
- r1["p"] = self.obj
- self.obj.child1 = P()
- tm1.get().commit()
-
- # start a new transaction with a new connection
- tm2 = transaction.TransactionManager()
- cn2 = self._db.open(mvcc=False, transaction_manager=tm2)
- # start a new transaction with the other connection
- r2 = cn2.root()
-
- self.assertEqual(r1._p_serial, r2._p_serial)
-
- self.obj.child2 = P()
- tm1.get().commit()
-
- # resume the transaction using cn2
- obj = r2["p"]
- # An attempt to access obj should fail, because r2 was read
- # earlier in the transaction and obj was modified by the othe
- # transaction.
- if shouldFail:
- self.assertRaises(ReadConflictError, lambda: obj.child1)
- # And since ReadConflictError was raised, attempting to commit
- # the transaction should re-raise it. checkNotIndependent()
- # failed this part of the test for a long time.
- self.assertRaises(ReadConflictError, tm2.get().commit)
-
- # And since that commit failed, trying to commit again should
- # fail again.
- self.assertRaises(TransactionFailedError, tm2.get().commit)
- # And again.
- self.assertRaises(TransactionFailedError, tm2.get().commit)
- # Etc.
- self.assertRaises(TransactionFailedError, tm2.get().commit)
-
- else:
- # make sure that accessing the object succeeds
- obj.child1
- tm2.get().abort()
-
- def checkReadConflictIgnored(self):
- # Test that an application that catches a read conflict and
- # continues can not commit the transaction later.
- root = self._db.open(mvcc=False).root()
- root["real_data"] = real_data = PersistentMapping()
- root["index"] = index = PersistentMapping()
-
- real_data["a"] = PersistentMapping({"indexed_value": 0})
- real_data["b"] = PersistentMapping({"indexed_value": 1})
- index[1] = PersistentMapping({"b": 1})
- index[0] = PersistentMapping({"a": 1})
- transaction.commit()
-
- # load some objects from one connection
- tm = transaction.TransactionManager()
- cn2 = self._db.open(mvcc=False, transaction_manager=tm)
- r2 = cn2.root()
- real_data2 = r2["real_data"]
- index2 = r2["index"]
-
- real_data["b"]["indexed_value"] = 0
- del index[1]["b"]
- index[0]["b"] = 1
- transaction.commit()
-
- del real_data2["a"]
- try:
- del index2[0]["a"]
- except ReadConflictError:
- # This is the crux of the text. Ignore the error.
- pass
- else:
- self.fail("No conflict occurred")
-
- # real_data2 still ready to commit
- self.assert_(real_data2._p_changed)
-
- # index2 values not ready to commit
- self.assert_(not index2._p_changed)
- self.assert_(not index2[0]._p_changed)
- self.assert_(not index2[1]._p_changed)
-
- self.assertRaises(ReadConflictError, tm.get().commit)
- self.assertRaises(TransactionFailedError, tm.get().commit)
- tm.get().abort()
-
- def checkIndependent(self):
- self.obj = Independent()
- self.readConflict(shouldFail=False)
-
- def checkNotIndependent(self):
- self.obj = DecoyIndependent()
- self.readConflict()
-
def checkSubtxnCommitDoesntGetInvalidations(self):
# Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed
# invalidations even for a subtxn commit. This could make
@@ -451,49 +347,6 @@
finally:
tm1.abort()
- def checkReadConflictErrorClearedDuringAbort(self):
- # When a transaction is aborted, the "memory" of which
- # objects were the cause of a ReadConflictError during
- # that transaction should be cleared.
- root = self._db.open(mvcc=False).root()
- data = PersistentMapping({'d': 1})
- root["data"] = data
- transaction.commit()
-
- # Provoke a ReadConflictError.
- tm2 = transaction.TransactionManager()
- cn2 = self._db.open(mvcc=False, transaction_manager=tm2)
- r2 = cn2.root()
- data2 = r2["data"]
-
- data['d'] = 2
- transaction.commit()
-
- try:
- data2['d'] = 3
- except ReadConflictError:
- pass
- else:
- self.fail("No conflict occurred")
-
- # Explicitly abort cn2's transaction.
- tm2.get().abort()
-
- # cn2 should retain no memory of the read conflict after an abort(),
- # but 3.2.3 had a bug wherein it did.
- data_conflicts = data._p_jar._conflicts
- data2_conflicts = data2._p_jar._conflicts
- self.failIf(data_conflicts)
- self.failIf(data2_conflicts) # this used to fail
-
- # And because of that, we still couldn't commit a change to data2['d']
- # in the new transaction.
- cn2.sync() # process the invalidation for data2['d']
- data2['d'] = 3
- tm2.get().commit() # 3.2.3 used to raise ReadConflictError
-
- cn2.close()
-
def checkTxnBeginImpliesAbort(self):
# begin() should do an abort() first, if needed.
cn = self._db.open()
@@ -762,8 +615,163 @@
transaction.abort()
conn.close()
-
+class ReadConflictTests(unittest.TestCase):
+ def setUp(self):
+ self._storage = ZODB.MappingStorage.MappingStorage()
+
+ def readConflict(self, shouldFail=True):
+ # Two transactions run concurrently. Each reads some object,
+ # then one commits and the other tries to read an object
+ # modified by the first. This read should fail with a conflict
+ # error because the object state read is not necessarily
+ # consistent with the objects read earlier in the transaction.
+
+ tm1 = transaction.TransactionManager()
+ conn = self._db.open(transaction_manager=tm1)
+ r1 = conn.root()
+ r1["p"] = self.obj
+ self.obj.child1 = P()
+ tm1.get().commit()
+
+ # start a new transaction with a new connection
+ tm2 = transaction.TransactionManager()
+ cn2 = self._db.open(transaction_manager=tm2)
+ # start a new transaction with the other connection
+ r2 = cn2.root()
+
+ self.assertEqual(r1._p_serial, r2._p_serial)
+
+ self.obj.child2 = P()
+ tm1.get().commit()
+
+ # resume the transaction using cn2
+ obj = r2["p"]
+ # An attempt to access obj should fail, because r2 was read
+ # earlier in the transaction and obj was modified by the othe
+ # transaction.
+ if shouldFail:
+ self.assertRaises(ReadConflictError, lambda: obj.child1)
+ # And since ReadConflictError was raised, attempting to commit
+ # the transaction should re-raise it. checkNotIndependent()
+ # failed this part of the test for a long time.
+ self.assertRaises(ReadConflictError, tm2.get().commit)
+
+ # And since that commit failed, trying to commit again should
+ # fail again.
+ self.assertRaises(TransactionFailedError, tm2.get().commit)
+ # And again.
+ self.assertRaises(TransactionFailedError, tm2.get().commit)
+ # Etc.
+ self.assertRaises(TransactionFailedError, tm2.get().commit)
+
+ else:
+ # make sure that accessing the object succeeds
+ obj.child1
+ tm2.get().abort()
+
+
+ def checkReadConflict(self):
+ self.obj = P()
+ self.readConflict()
+
+ def checkIndependent(self):
+ self.obj = Independent()
+ self.readConflict(shouldFail=False)
+
+ def checkNotIndependent(self):
+ self.obj = DecoyIndependent()
+ self.readConflict()
+
+ def checkReadConflictIgnored(self):
+ # Test that an application that catches a read conflict and
+ # continues can not commit the transaction later.
+ root = self._db.open().root()
+ root["real_data"] = real_data = PersistentMapping()
+ root["index"] = index = PersistentMapping()
+
+ real_data["a"] = PersistentMapping({"indexed_value": 0})
+ real_data["b"] = PersistentMapping({"indexed_value": 1})
+ index[1] = PersistentMapping({"b": 1})
+ index[0] = PersistentMapping({"a": 1})
+ transaction.commit()
+
+ # load some objects from one connection
+ tm = transaction.TransactionManager()
+ cn2 = self._db.open(transaction_manager=tm)
+ r2 = cn2.root()
+ real_data2 = r2["real_data"]
+ index2 = r2["index"]
+
+ real_data["b"]["indexed_value"] = 0
+ del index[1]["b"]
+ index[0]["b"] = 1
+ transaction.commit()
+
+ del real_data2["a"]
+ try:
+ del index2[0]["a"]
+ except ReadConflictError:
+ # This is the crux of the text. Ignore the error.
+ pass
+ else:
+ self.fail("No conflict occurred")
+
+ # real_data2 still ready to commit
+ self.assert_(real_data2._p_changed)
+
+ # index2 values not ready to commit
+ self.assert_(not index2._p_changed)
+ self.assert_(not index2[0]._p_changed)
+ self.assert_(not index2[1]._p_changed)
+
+ self.assertRaises(ReadConflictError, tm.get().commit)
+ self.assertRaises(TransactionFailedError, tm.get().commit)
+ tm.get().abort()
+
+ def checkReadConflictErrorClearedDuringAbort(self):
+ # When a transaction is aborted, the "memory" of which
+ # objects were the cause of a ReadConflictError during
+ # that transaction should be cleared.
+ root = self._db.open().root()
+ data = PersistentMapping({'d': 1})
+ root["data"] = data
+ transaction.commit()
+
+ # Provoke a ReadConflictError.
+ tm2 = transaction.TransactionManager()
+ cn2 = self._db.open(transaction_manager=tm2)
+ r2 = cn2.root()
+ data2 = r2["data"]
+
+ data['d'] = 2
+ transaction.commit()
+
+ try:
+ data2['d'] = 3
+ except ReadConflictError:
+ pass
+ else:
+ self.fail("No conflict occurred")
+
+ # Explicitly abort cn2's transaction.
+ tm2.get().abort()
+
+ # cn2 should retain no memory of the read conflict after an abort(),
+ # but 3.2.3 had a bug wherein it did.
+ data_conflicts = data._p_jar._conflicts
+ data2_conflicts = data2._p_jar._conflicts
+ self.failIf(data_conflicts)
+ self.failIf(data2_conflicts) # this used to fail
+
+ # And because of that, we still couldn't commit a change to data2['d']
+ # in the new transaction.
+ cn2.sync() # process the invalidation for data2['d']
+ data2['d'] = 3
+ tm2.get().commit() # 3.2.3 used to raise ReadConflictError
+
+ cn2.close()
+
class PoisonedError(Exception):
pass
More information about the Zodb-checkins
mailing list