Commit e3bae622 authored by Jim Fulton's avatar Jim Fulton

Removed the synch option to DB and Connection open.

Removed subtransaction support.
parent 8158dcec
...@@ -88,8 +88,6 @@ class Connection(ExportImport, object): ...@@ -88,8 +88,6 @@ class Connection(ExportImport, object):
# Multi-database support # Multi-database support
self.connections = {self._db.database_name: self} self.connections = {self._db.database_name: self}
self._synch = None
self._version = version self._version = version
self._normal_storage = self._storage = db._storage self._normal_storage = self._storage = db._storage
self.new_oid = db._storage.new_oid self.new_oid = db._storage.new_oid
...@@ -283,9 +281,8 @@ class Connection(ExportImport, object): ...@@ -283,9 +281,8 @@ class Connection(ExportImport, object):
self._debug_info = () self._debug_info = ()
if self._synch: if self._opened:
self.transaction_manager.unregisterSynch(self) self.transaction_manager.unregisterSynch(self)
self._synch = None
if primary: if primary:
for connection in self.connections.values(): for connection in self.connections.values():
...@@ -347,7 +344,7 @@ class Connection(ExportImport, object): ...@@ -347,7 +344,7 @@ class Connection(ExportImport, object):
if connection is None: if connection is None:
new_con = self._db.databases[database_name].open( new_con = self._db.databases[database_name].open(
transaction_manager=self.transaction_manager, transaction_manager=self.transaction_manager,
version=self._version, synch=self._synch, version=self._version,
) )
self.connections.update(new_con.connections) self.connections.update(new_con.connections)
new_con.connections = self.connections new_con.connections = self.connections
...@@ -452,8 +449,6 @@ class Connection(ExportImport, object): ...@@ -452,8 +449,6 @@ class Connection(ExportImport, object):
def _tpc_cleanup(self): def _tpc_cleanup(self):
"""Performs cleanup operations to support tpc_finish and tpc_abort.""" """Performs cleanup operations to support tpc_finish and tpc_abort."""
self._conflicts.clear() self._conflicts.clear()
if not self._synch:
self._flush_invalidations()
self._needs_to_join = True self._needs_to_join = True
self._registered_objects = [] self._registered_objects = []
self._creating.clear() self._creating.clear()
...@@ -648,12 +643,6 @@ class Connection(ExportImport, object): ...@@ -648,12 +643,6 @@ class Connection(ExportImport, object):
# return an exception object and expect that the Connection # return an exception object and expect that the Connection
# will raise the exception. # will raise the exception.
# When commit_sub() exceutes a store, there is no need to
# update the _p_changed flag, because the subtransaction
# tpc_vote() calls already did this. The change=1 argument
# exists to allow commit_sub() to avoid setting the flag
# again.
# When conflict resolution occurs, the object state held by # When conflict resolution occurs, the object state held by
# the connection does not match what is written to the # the connection does not match what is written to the
# database. Invalidate the object here to guarantee that # database. Invalidate the object here to guarantee that
...@@ -970,7 +959,7 @@ class Connection(ExportImport, object): ...@@ -970,7 +959,7 @@ class Connection(ExportImport, object):
# return a list of [ghosts....not recently used.....recently used] # return a list of [ghosts....not recently used.....recently used]
return everything.items() + items return everything.items() + items
def open(self, transaction_manager=None, synch=True, delegate=True): def open(self, transaction_manager=None, delegate=True):
"""Register odb, the DB that this Connection uses. """Register odb, the DB that this Connection uses.
This method is called by the DB every time a Connection This method is called by the DB every time a Connection
...@@ -984,16 +973,10 @@ class Connection(ExportImport, object): ...@@ -984,16 +973,10 @@ class Connection(ExportImport, object):
odb: database that owns the Connection odb: database that owns the Connection
transaction_manager: transaction manager to use. None means transaction_manager: transaction manager to use. None means
use the default transaction manager. use the default transaction manager.
synch: boolean indicating whether Connection should
register for afterCompletion() calls. 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
# and Storage.
self._opened = time() self._opened = time()
self._synch = synch
if transaction_manager is None: if transaction_manager is None:
transaction_manager = transaction.manager transaction_manager = transaction.manager
...@@ -1006,7 +989,6 @@ class Connection(ExportImport, object): ...@@ -1006,7 +989,6 @@ class Connection(ExportImport, object):
else: else:
self._flush_invalidations() self._flush_invalidations()
if synch:
transaction_manager.registerSynch(self) transaction_manager.registerSynch(self)
if self._cache is not None: if self._cache is not None:
...@@ -1016,7 +998,7 @@ class Connection(ExportImport, object): ...@@ -1016,7 +998,7 @@ class Connection(ExportImport, object):
# delegate open to secondary connections # delegate open to secondary connections
for connection in self.connections.values(): for connection in self.connections.values():
if connection is not self: if connection is not self:
connection.open(transaction_manager, synch, False) connection.open(transaction_manager, False)
def _resetCache(self): def _resetCache(self):
"""Creates a new cache, discarding the old one. """Creates a new cache, discarding the old one.
...@@ -1111,7 +1093,7 @@ class Connection(ExportImport, object): ...@@ -1111,7 +1093,7 @@ class Connection(ExportImport, object):
src.reset(*state) src.reset(*state)
def _commit_savepoint(self, transaction): def _commit_savepoint(self, transaction):
"""Commit all changes made in subtransactions and begin 2-phase commit """Commit all changes made in savepoints and begin 2-phase commit
""" """
src = self._savepoint_storage src = self._savepoint_storage
self._storage = self._normal_storage self._storage = self._normal_storage
...@@ -1143,7 +1125,7 @@ class Connection(ExportImport, object): ...@@ -1143,7 +1125,7 @@ class Connection(ExportImport, object):
src.close() src.close()
def _abort_savepoint(self): def _abort_savepoint(self):
"""Discard all subtransaction data.""" """Discard all savepoint data."""
src = self._savepoint_storage src = self._savepoint_storage
self._storage = self._normal_storage self._storage = self._normal_storage
self._savepoint_storage = None self._savepoint_storage = None
......
...@@ -554,7 +554,7 @@ class DB(object): ...@@ -554,7 +554,7 @@ class DB(object):
def objectCount(self): def objectCount(self):
return len(self._storage) return len(self._storage)
def open(self, version='', transaction_manager=None, synch=True): def open(self, version='', transaction_manager=None):
"""Return a database Connection for use by application code. """Return a database Connection for use by application code.
The optional `version` argument can be used to specify that a The optional `version` argument can be used to specify that a
...@@ -569,8 +569,6 @@ class DB(object): ...@@ -569,8 +569,6 @@ class DB(object):
in, defaults to no version. in, defaults to no version.
- `transaction_manager`: transaction manager to use. None means - `transaction_manager`: transaction manager to use. None means
use the default transaction manager. use the default transaction manager.
- `synch`: boolean indicating whether Connection should
register for afterCompletion() calls.
""" """
if version: if version:
...@@ -607,7 +605,7 @@ class DB(object): ...@@ -607,7 +605,7 @@ class DB(object):
assert result is not None assert result is not None
# Tell the connection it belongs to self. # Tell the connection it belongs to self.
result.open(transaction_manager, synch) result.open(transaction_manager)
# A good time to do some cache cleanup. # A good time to do some cache cleanup.
self._connectionMap(lambda c: c.cacheGC()) self._connectionMap(lambda c: c.cacheGC())
......
...@@ -355,20 +355,13 @@ class IDatabase(IStorageDB): ...@@ -355,20 +355,13 @@ class IDatabase(IStorageDB):
entry. entry.
""") """)
def open(version='', def open(version='', transaction_manager=None):
mvcc=True,
transaction_manager=None,
synch=True
):
"""Return an IConnection object for use by application code. """Return an IConnection object for use by application code.
version: the "version" that all changes will be made version: the "version" that all changes will be made
in, defaults to no version. in, defaults to no version.
mvcc: boolean indicating whether MVCC is enabled
transaction_manager: transaction manager to use. None means transaction_manager: transaction manager to use. None means
use the default transaction manager. use the default transaction manager.
synch: boolean indicating whether Connection should
register for afterCompletion() calls.
Note that the connection pool is managed as a stack, to Note that the connection pool is managed as a stack, to
increase the likelihood that the connection's stack will increase the likelihood that the connection's stack will
......
...@@ -24,12 +24,6 @@ from ZODB.utils import p64, u64 ...@@ -24,12 +24,6 @@ from ZODB.utils import p64, u64
from ZODB.tests.warnhook import WarningsHook from ZODB.tests.warnhook import WarningsHook
from zope.interface.verify import verifyObject from zope.interface.verify import verifyObject
# deprecated37 remove when subtransactions go away
# Don't complain about subtxns in these tests.
warnings.filterwarnings("ignore",
".*\nsubtransactions are deprecated",
DeprecationWarning, __name__)
class ConnectionDotAdd(unittest.TestCase): class ConnectionDotAdd(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -292,35 +286,6 @@ class UserMethodTests(unittest.TestCase): ...@@ -292,35 +286,6 @@ class UserMethodTests(unittest.TestCase):
10 10
>>> cn.close(); cn2.close() >>> cn.close(); cn2.close()
Bug: We weren't catching the case where the only changes pending
were in a subtransaction.
>>> cn = db.open()
>>> cn.root()['a'] = 100
>>> transaction.commit(True)
>>> cn.close() # this was succeeding
Traceback (most recent call last):
...
ConnectionStateError: Cannot close a connection joined to a transaction
Again this leaves the connection as it was.
>>> transaction.commit()
>>> cn2 = db.open()
>>> cn2.root()['a']
100
>>> cn.close(); cn2.close()
Make sure we can still close a connection after aborting a pending
subtransaction.
>>> cn = db.open()
>>> cn.root()['a'] = 1000
>>> transaction.commit(True)
>>> cn.root()['a']
1000
>>> transaction.abort()
>>> cn.root()['a']
100
>>> cn.close()
>>> db.close() >>> db.close()
""" """
......
...@@ -54,10 +54,7 @@ savepoint. ...@@ -54,10 +54,7 @@ savepoint.
The problem was that we were effectively commiting the object twice -- The problem was that we were effectively commiting the object twice --
when commiting the current data and when committing the savepoint. when commiting the current data and when committing the savepoint.
The fix was to first make a new savepoint to move new changes to the The fix was to first make a new savepoint to move new changes to the
savepoint storage and *then* to commit the savepoint storage. (This is savepoint storage and *then* to commit the savepoint storage.
similar to the strategy that was used for subtransactions prior to
savepoints.)
>>> import ZODB.tests.util >>> import ZODB.tests.util
>>> db = ZODB.tests.util.DB() >>> db = ZODB.tests.util.DB()
...@@ -90,8 +87,7 @@ def testSavepointDoesCacheGC(): ...@@ -90,8 +87,7 @@ def testSavepointDoesCacheGC():
"""\ """\
Although the interface doesn't guarantee this internal detail, making a Although the interface doesn't guarantee this internal detail, making a
savepoint should do incremental gc on connection memory caches. Indeed, savepoint should do incremental gc on connection memory caches. Indeed,
one traditional use for savepoints (started by the older, related one traditional use for savepoints is simply to free memory space midstream
"subtransaction commit" idea) is simply to free memory space midstream
during a long transaction. Before ZODB 3.4.2, making a savepoint failed during a long transaction. Before ZODB 3.4.2, making a savepoint failed
to trigger cache gc, and this test verifies that it now does. to trigger cache gc, and this test verifies that it now does.
......
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""
ZODB subtransaction tests
=========================
Subtransactions are deprecated. First we install a hook, to verify that
deprecation warnings are generated.
>>> hook = WarningsHook()
>>> hook.install()
Subtransactions are provided by a generic transaction interface, but
only supported by ZODB. These tests verify that some of the important
cases work as expected.
>>> import transaction
>>> from ZODB import DB
>>> from ZODB.tests.test_storage import MinimalMemoryStorage
>>> from ZODB.tests.MinPO import MinPO
First create a few objects in the database root with a normal commit.
We're going to make a series of modifications to these objects.
>>> db = DB(MinimalMemoryStorage())
>>> cn = db.open()
>>> rt = cn.root()
>>> def init():
... global a, b, c
... a = rt["a"] = MinPO("a0")
... b = rt["b"] = MinPO("b0")
... c = rt["c"] = MinPO("c0")
... transaction.commit()
>>> init()
We'll also open a second database connection and use it to verify that
the intermediate results of subtransactions are not visible to other
connections.
>>> cn2 = db.open(synch=False)
>>> rt2 = cn2.root()
>>> shadow_a = rt2["a"]
>>> shadow_b = rt2["b"]
>>> shadow_c = rt2["c"]
Subtransaction commit
---------------------
We'll make a series of modifications in subtransactions.
>>> a.value = "a1"
>>> b.value = "b1"
>>> transaction.commit(1)
>>> a.value, b.value
('a1', 'b1')
>>> shadow_a.value, shadow_b.value
('a0', 'b0')
The subtransaction commit should have generated a deprecation wng:
>>> len(hook.warnings)
1
>>> message, category, filename, lineno = hook.warnings[0]
>>> print message
This will be removed in ZODB 3.7:
subtransactions are deprecated; use transaction.savepoint() instead of \
transaction.commit(1)
>>> category.__name__
'DeprecationWarning'
>>> hook.clear()
>>> a.value = "a2"
>>> c.value = "c1"
>>> transaction.commit(1)
>>> a.value, c.value
('a2', 'c1')
>>> shadow_a.value, shadow_c.value
('a0', 'c0')
>>> a.value = "a3"
>>> transaction.commit(1)
>>> a.value
'a3'
>>> shadow_a.value
'a0'
>>> transaction.commit()
>>> a.value, b.value, c.value
('a3', 'b1', 'c1')
Subtransaction with nested abort
--------------------------------
>>> init()
>>> a.value = "a1"
>>> transaction.commit(1)
>>> b.value = "b1"
>>> transaction.commit(1)
A sub-transaction abort will undo current changes, reverting to the
database state as of the last sub-transaction commit. There is
(apparently) no way to abort an already-committed subtransaction.
>>> c.value = "c1"
>>> transaction.abort(1)
>>> a.value, b.value, c.value
('a1', 'b1', 'c0')
The subtxn abort should also have generated a deprecation warning:
>>> len(hook.warnings)
1
>>> message, category, filename, lineno = hook.warnings[0]
>>> print message
This will be removed in ZODB 3.7:
subtransactions are deprecated; use sp.rollback() instead of \
transaction.abort(1), where `sp` is the corresponding savepoint \
captured earlier
>>> category.__name__
'DeprecationWarning'
>>> hook.clear()
Multiple aborts have no extra effect.
>>> transaction.abort(1)
>>> a.value, b.value, c.value
('a1', 'b1', 'c0')
>>> transaction.commit()
>>> a.value, b.value, c.value
('a1', 'b1', 'c0')
Subtransaction with top-level abort
-----------------------------------
>>> init()
>>> a.value = "a1"
>>> transaction.commit(1)
>>> b.value = "b1"
>>> transaction.commit(1)
A sub-transaction abort will undo current changes, reverting to the
database state as of the last sub-transaction commit. There is
(apparently) no way to abort an already-committed subtransaction.
>>> c.value = "c1"
>>> transaction.abort(1)
>>> transaction.abort()
>>> a.value, b.value, c.value
('a0', 'b0', 'c0')
We have to uninstall the hook so that other warnings don't get lost.
>>> len(hook.warnings) # we don't expect we captured other warnings
0
>>> hook.uninstall()
"""
from ZODB.tests.warnhook import WarningsHook
from zope.testing import doctest
def test_suite():
return doctest.DocTestSuite()
...@@ -25,11 +25,7 @@ from persistent import Persistent ...@@ -25,11 +25,7 @@ from persistent import Persistent
from persistent.mapping import PersistentMapping from persistent.mapping import PersistentMapping
import transaction import transaction
# deprecated37 remove when subtransactions go away # deprecated39 remove when versions go away
# Don't complain about subtxns in these tests.
warnings.filterwarnings("ignore",
".*\nsubtransactions are deprecated",
DeprecationWarning, __name__)
warnings.filterwarnings("ignore", warnings.filterwarnings("ignore",
"Versions are deprecated", "Versions are deprecated",
DeprecationWarning, __name__) DeprecationWarning, __name__)
...@@ -217,70 +213,6 @@ class ZODBTests(unittest.TestCase): ...@@ -217,70 +213,6 @@ class ZODBTests(unittest.TestCase):
conn1.close() conn1.close()
conn2.close() conn2.close()
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
# inconsistent state visible after a subtxn commit. There was a
# suspicion that POSKeyError was possible as a result, but I wasn't
# able to construct a case where that happened.
# Set up the database, to hold
# root --> "p" -> value = 1
# --> "q" -> value = 2
tm1 = transaction.TransactionManager()
conn = self._db.open(transaction_manager=tm1)
r1 = conn.root()
p = P()
p.value = 1
r1["p"] = p
q = P()
q.value = 2
r1["q"] = q
tm1.commit()
# Now txn T1 changes p.value to 3 locally (subtxn commit).
p.value = 3
tm1.commit(True)
# Start new txn T2 with a new connection.
tm2 = transaction.TransactionManager()
cn2 = self._db.open(transaction_manager=tm2)
r2 = cn2.root()
p2 = r2["p"]
self.assertEqual(p._p_oid, p2._p_oid)
# T2 shouldn't see T1's change of p.value to 3, because T1 didn't
# commit yet.
self.assertEqual(p2.value, 1)
# Change p.value to 4, and q.value to 5. Neither should be visible
# to T1, because T1 is still in progress.
p2.value = 4
q2 = r2["q"]
self.assertEqual(q._p_oid, q2._p_oid)
self.assertEqual(q2.value, 2)
q2.value = 5
tm2.commit()
# Back to T1. p and q still have the expected values.
rt = conn.root()
self.assertEqual(rt["p"].value, 3)
self.assertEqual(rt["q"].value, 2)
# Now do another subtxn commit in T1. This shouldn't change what
# T1 sees for p and q.
rt["r"] = P()
tm1.commit(True)
# Doing that subtxn commit in T1 should not process invalidations
# from T2's commit. p.value should still be 3 here (because that's
# what T1 subtxn-committed earlier), and q.value should still be 2.
# Prior to ZODB 3.2.9 and 3.4, q.value was 5 here.
rt = conn.root()
try:
self.assertEqual(rt["p"].value, 3)
self.assertEqual(rt["q"].value, 2)
finally:
tm1.abort()
def checkSavepointDoesntGetInvalidations(self): def checkSavepointDoesntGetInvalidations(self):
# Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed # Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed
# invalidations even for a subtxn commit. This could make # invalidations even for a subtxn commit. This could make
...@@ -357,23 +289,14 @@ class ZODBTests(unittest.TestCase): ...@@ -357,23 +289,14 @@ class ZODBTests(unittest.TestCase):
rt = cn.root() rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a') self.assertRaises(KeyError, rt.__getitem__, 'a')
# A longstanding bug: this didn't work if changes were only in
# subtransactions.
transaction.begin()
rt = cn.root()
rt['a'] = 2
transaction.commit(1)
transaction.begin() transaction.begin()
rt = cn.root() rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a') self.assertRaises(KeyError, rt.__getitem__, 'a')
# One more time, mixing "top level" and subtransaction changes. # One more time.
transaction.begin() transaction.begin()
rt = cn.root() rt = cn.root()
rt['a'] = 3 rt['a'] = 3
transaction.commit(1)
rt['b'] = 4
transaction.begin() transaction.begin()
rt = cn.root() rt = cn.root()
...@@ -389,7 +312,6 @@ class ZODBTests(unittest.TestCase): ...@@ -389,7 +312,6 @@ class ZODBTests(unittest.TestCase):
# rest of this test was tossed. # rest of this test was tossed.
def checkFailingCommitSticks(self): def checkFailingCommitSticks(self):
# See also checkFailingSubtransactionCommitSticks.
# See also checkFailingSavepointSticks. # See also checkFailingSavepointSticks.
cn = self._db.open() cn = self._db.open()
rt = cn.root() rt = cn.root()
...@@ -435,93 +357,6 @@ class ZODBTests(unittest.TestCase): ...@@ -435,93 +357,6 @@ class ZODBTests(unittest.TestCase):
cn.close() cn.close()
def checkFailingSubtransactionCommitSticks(self):
cn = self._db.open()
rt = cn.root()
rt['a'] = 1
transaction.commit(True)
self.assertEqual(rt['a'], 1)
rt['b'] = 2
# Make a jar that raises PoisonedError when a subtxn commit is done.
poisoned = PoisonedJar(break_savepoint=True)
transaction.get().join(poisoned)
# We're using try/except here instead of assertRaises so that this
# module's attempt to suppress subtransaction deprecation wngs
# works.
try:
transaction.commit(True)
except PoisonedError:
pass
else:
self.fail("expected PoisonedError")
# Trying to subtxn-commit again fails too.
try:
transaction.commit(True)
except TransactionFailedError:
pass
else:
self.fail("expected TransactionFailedError")
try:
transaction.commit(True)
except TransactionFailedError:
pass
else:
self.fail("expected TransactionFailedError")
# Top-level commit also fails.
self.assertRaises(TransactionFailedError, transaction.commit)
# The changes to rt['a'] and rt['b'] are lost.
self.assertRaises(KeyError, rt.__getitem__, 'a')
self.assertRaises(KeyError, rt.__getitem__, 'b')
# Trying to modify an object also fails, because Transaction.join()
# also raises TransactionFailedError.
self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2)
# Clean up via abort(), and try again.
transaction.abort()
rt['a'] = 1
transaction.commit()
self.assertEqual(rt['a'], 1)
# Cleaning up via begin() should also work.
rt['a'] = 2
transaction.get().join(poisoned)
try:
transaction.commit(True)
except PoisonedError:
pass
else:
self.fail("expected PoisonedError")
# Trying to subtxn-commit again fails too.
try:
transaction.commit(True)
except TransactionFailedError:
pass
else:
self.fail("expected TransactionFailedError")
# The change to rt['a'] is lost.
self.assertEqual(rt['a'], 1)
# Trying to modify an object also fails.
self.assertRaises(TransactionFailedError, rt.__setitem__, 'b', 2)
# Clean up via begin(), and try again.
transaction.begin()
rt['a'] = 2
transaction.commit(True)
self.assertEqual(rt['a'], 2)
transaction.get().commit()
cn2 = self._db.open()
rt = cn.root()
self.assertEqual(rt['a'], 2)
cn.close()
cn2.close()
def checkFailingSavepointSticks(self): def checkFailingSavepointSticks(self):
cn = self._db.open() cn = self._db.open()
rt = cn.root() rt = cn.root()
...@@ -786,8 +621,6 @@ class PoisonedJar: ...@@ -786,8 +621,6 @@ class PoisonedJar:
def sortKey(self): def sortKey(self):
return str(id(self)) return str(id(self))
# A way that used to poison a subtransaction commit. With the current
# implementation of subtxns, pass break_savepoint=True instead.
def tpc_begin(self, *args): def tpc_begin(self, *args):
if self.break_tpc_begin: if self.break_tpc_begin:
raise PoisonedError("tpc_begin fails") raise PoisonedError("tpc_begin fails")
......
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