Commit a910b691 authored by Jim Fulton's avatar Jim Fulton

Removed the synch option to DB and Connection open.

Removed subtransaction support.
parent f73688b1
......@@ -88,8 +88,6 @@ class Connection(ExportImport, object):
# Multi-database support
self.connections = {self._db.database_name: self}
self._synch = None
self._version = version
self._normal_storage = self._storage = db._storage
self.new_oid = db._storage.new_oid
......@@ -283,9 +281,8 @@ class Connection(ExportImport, object):
self._debug_info = ()
if self._synch:
if self._opened:
self.transaction_manager.unregisterSynch(self)
self._synch = None
if primary:
for connection in self.connections.values():
......@@ -347,7 +344,7 @@ class Connection(ExportImport, object):
if connection is None:
new_con = self._db.databases[database_name].open(
transaction_manager=self.transaction_manager,
version=self._version, synch=self._synch,
version=self._version,
)
self.connections.update(new_con.connections)
new_con.connections = self.connections
......@@ -452,8 +449,6 @@ class Connection(ExportImport, object):
def _tpc_cleanup(self):
"""Performs cleanup operations to support tpc_finish and tpc_abort."""
self._conflicts.clear()
if not self._synch:
self._flush_invalidations()
self._needs_to_join = True
self._registered_objects = []
self._creating.clear()
......@@ -648,12 +643,6 @@ class Connection(ExportImport, object):
# return an exception object and expect that the Connection
# 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
# the connection does not match what is written to the
# database. Invalidate the object here to guarantee that
......@@ -970,7 +959,7 @@ class Connection(ExportImport, object):
# return a list of [ghosts....not recently used.....recently used]
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.
This method is called by the DB every time a Connection
......@@ -984,16 +973,10 @@ class Connection(ExportImport, object):
odb: database that owns the Connection
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
# and Storage.
self._opened = time()
self._synch = synch
if transaction_manager is None:
transaction_manager = transaction.manager
......@@ -1006,8 +989,7 @@ class Connection(ExportImport, object):
else:
self._flush_invalidations()
if synch:
transaction_manager.registerSynch(self)
transaction_manager.registerSynch(self)
if self._cache is not None:
self._cache.incrgc() # This is a good time to do some GC
......@@ -1016,7 +998,7 @@ class Connection(ExportImport, object):
# delegate open to secondary connections
for connection in self.connections.values():
if connection is not self:
connection.open(transaction_manager, synch, False)
connection.open(transaction_manager, False)
def _resetCache(self):
"""Creates a new cache, discarding the old one.
......@@ -1111,7 +1093,7 @@ class Connection(ExportImport, object):
src.reset(*state)
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
self._storage = self._normal_storage
......@@ -1143,7 +1125,7 @@ class Connection(ExportImport, object):
src.close()
def _abort_savepoint(self):
"""Discard all subtransaction data."""
"""Discard all savepoint data."""
src = self._savepoint_storage
self._storage = self._normal_storage
self._savepoint_storage = None
......
......@@ -554,7 +554,7 @@ class DB(object):
def objectCount(self):
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.
The optional `version` argument can be used to specify that a
......@@ -569,8 +569,6 @@ class DB(object):
in, defaults to no version.
- `transaction_manager`: transaction manager to use. None means
use the default transaction manager.
- `synch`: boolean indicating whether Connection should
register for afterCompletion() calls.
"""
if version:
......@@ -607,7 +605,7 @@ class DB(object):
assert result is not None
# Tell the connection it belongs to self.
result.open(transaction_manager, synch)
result.open(transaction_manager)
# A good time to do some cache cleanup.
self._connectionMap(lambda c: c.cacheGC())
......
......@@ -355,20 +355,13 @@ class IDatabase(IStorageDB):
entry.
""")
def open(version='',
mvcc=True,
transaction_manager=None,
synch=True
):
def open(version='', transaction_manager=None):
"""Return an IConnection object for use by application code.
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
register for afterCompletion() calls.
Note that the connection pool is managed as a stack, to
increase the likelihood that the connection's stack will
......
......@@ -24,12 +24,6 @@ from ZODB.utils import p64, u64
from ZODB.tests.warnhook import WarningsHook
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):
def setUp(self):
......@@ -292,35 +286,6 @@ class UserMethodTests(unittest.TestCase):
10
>>> 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()
"""
......
......@@ -54,10 +54,7 @@ savepoint.
The problem was that we were effectively commiting the object twice --
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
savepoint storage and *then* to commit the savepoint storage. (This is
similar to the strategy that was used for subtransactions prior to
savepoints.)
savepoint storage and *then* to commit the savepoint storage.
>>> import ZODB.tests.util
>>> db = ZODB.tests.util.DB()
......@@ -90,8 +87,7 @@ def testSavepointDoesCacheGC():
"""\
Although the interface doesn't guarantee this internal detail, making a
savepoint should do incremental gc on connection memory caches. Indeed,
one traditional use for savepoints (started by the older, related
"subtransaction commit" idea) is simply to free memory space midstream
one traditional use for savepoints is simply to free memory space midstream
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.
......
##############################################################################
#
# 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
from persistent.mapping import PersistentMapping
import transaction
# deprecated37 remove when subtransactions go away
# Don't complain about subtxns in these tests.
warnings.filterwarnings("ignore",
".*\nsubtransactions are deprecated",
DeprecationWarning, __name__)
# deprecated39 remove when versions go away
warnings.filterwarnings("ignore",
"Versions are deprecated",
DeprecationWarning, __name__)
......@@ -217,70 +213,6 @@ class ZODBTests(unittest.TestCase):
conn1.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):
# Prior to ZODB 3.2.9 and 3.4, Connection.tpc_finish() processed
# invalidations even for a subtxn commit. This could make
......@@ -357,23 +289,14 @@ class ZODBTests(unittest.TestCase):
rt = cn.root()
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()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# One more time, mixing "top level" and subtransaction changes.
# One more time.
transaction.begin()
rt = cn.root()
rt['a'] = 3
transaction.commit(1)
rt['b'] = 4
transaction.begin()
rt = cn.root()
......@@ -389,7 +312,6 @@ class ZODBTests(unittest.TestCase):
# rest of this test was tossed.
def checkFailingCommitSticks(self):
# See also checkFailingSubtransactionCommitSticks.
# See also checkFailingSavepointSticks.
cn = self._db.open()
rt = cn.root()
......@@ -435,93 +357,6 @@ class ZODBTests(unittest.TestCase):
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):
cn = self._db.open()
rt = cn.root()
......@@ -786,8 +621,6 @@ class PoisonedJar:
def sortKey(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):
if self.break_tpc_begin:
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