Commit f73688b1 authored by Jim Fulton's avatar Jim Fulton

Removed subtransaction support, which had been scheduled to go in 3.7.

parent d245b891
...@@ -89,24 +89,11 @@ class TransactionManager(object): ...@@ -89,24 +89,11 @@ class TransactionManager(object):
def doom(self): def doom(self):
return self.get().doom() return self.get().doom()
def commit(self, sub=_marker): def commit(self):
if sub is _marker: return self.get().commit()
sub = None
else: def abort(self):
deprecated37("subtransactions are deprecated; use " return self.get().abort()
"transaction.savepoint() instead of "
"transaction.commit(1)")
return self.get().commit(sub, deprecation_wng=False)
def abort(self, sub=_marker):
if sub is _marker:
sub = None
else:
deprecated37("subtransactions are deprecated; use "
"sp.rollback() instead of "
"transaction.abort(1), where `sp` is the "
"corresponding savepoint captured earlier")
return self.get().abort(sub, deprecation_wng=False)
def savepoint(self, optimistic=False): def savepoint(self, optimistic=False):
return self.get().savepoint(optimistic) return self.get().savepoint(optimistic)
......
...@@ -27,54 +27,6 @@ resource manager and adds it to the list of resources. register() is ...@@ -27,54 +27,6 @@ resource manager and adds it to the list of resources. register() is
for backwards compatibility. It takes a persistent object and for backwards compatibility. It takes a persistent object and
registers its _p_jar attribute. TODO: explain adapter registers its _p_jar attribute. TODO: explain adapter
Subtransactions
---------------
Note: Subtransactions are deprecated! Use savepoint/rollback instead.
A subtransaction applies the transaction notion recursively. It
allows a set of modifications within a transaction to be committed or
aborted as a group. A subtransaction is a strictly local activity;
its changes are not visible to any other database connection until the
top-level transaction commits. In addition to its use to organize a
large transaction, subtransactions can be used to optimize memory use.
ZODB must keep modified objects in memory until a transaction commits
and it can write the changes to the storage. A subtransaction uses a
temporary disk storage for its commits, allowing modified objects to
be flushed from memory when the subtransaction commits.
The commit() and abort() methods take an optional subtransaction
argument that defaults to false. If it is a true, the operation is
performed on a subtransaction.
Subtransactions add a lot of complexity to the transaction
implementation. Some resource managers support subtransactions, but
they are not required to. (ZODB Connection is the only standard
resource manager that supports subtransactions.) Resource managers
that do support subtransactions implement abort_sub() and commit_sub()
methods and support a second argument to tpc_begin().
The second argument to tpc_begin() indicates that a subtransaction
commit is beginning (if it is true). In a subtransaction, there is no
tpc_vote() call, because sub-transactions don't need 2-phase commit.
If a sub-transaction abort or commit fails, we can abort the outer
transaction. The tpc_finish() or tpc_abort() call applies just to
that subtransaction.
Once a resource manager is involved in a subtransaction, all
subsequent transactions will be treated as subtransactions until
abort_sub() or commit_sub() is called. abort_sub() will undo all the
changes of the subtransactions. commit_sub() will begin a top-level
transaction and store all the changes from subtransactions. After
commit_sub(), the transaction must still call tpc_vote() and
tpc_finish().
If the resource manager does not support subtransactions, nothing
happens when the subtransaction commits. Instead, the resource
manager is put on a list of managers to commit when the actual
top-level transaction commits. If this happens, it will not be
possible to abort subtransactions.
Two-phase commit Two-phase commit
---------------- ----------------
...@@ -89,21 +41,6 @@ them. ...@@ -89,21 +41,6 @@ them.
3. tpc_vote(txn) 3. tpc_vote(txn)
4. tpc_finish(txn) 4. tpc_finish(txn)
Subtransaction commit
---------------------
Note: Subtransactions are deprecated!
When a subtransaction commits, the protocol is different.
1. tpc_begin() is passed a second argument, which indicates that a
subtransaction is being committed.
2. tpc_vote() is not called.
Once a subtransaction has been committed, the top-level transaction
commit will start with a commit_sub() call instead of a tpc_begin()
call.
Before-commit hook Before-commit hook
------------------ ------------------
...@@ -212,9 +149,6 @@ class Transaction(object): ...@@ -212,9 +149,6 @@ class Transaction(object):
# savepoint to its index (see above). # savepoint to its index (see above).
_savepoint2index = None _savepoint2index = None
# Remember the savepoint for the last subtransaction.
_subtransaction_savepoint = None
# Meta data. ._extension is also metadata, but is initialized to an # Meta data. ._extension is also metadata, but is initialized to an
# emtpy dict in __init__. # emtpy dict in __init__.
user = "" user = ""
...@@ -372,29 +306,13 @@ class Transaction(object): ...@@ -372,29 +306,13 @@ class Transaction(object):
assert id(obj) not in map(id, adapter.objects) assert id(obj) not in map(id, adapter.objects)
adapter.objects.append(obj) adapter.objects.append(obj)
def commit(self, subtransaction=_marker, deprecation_wng=True): def commit(self):
if self.status is Status.DOOMED: if self.status is Status.DOOMED:
raise interfaces.DoomedTransaction() raise interfaces.DoomedTransaction()
if subtransaction is _marker:
subtransaction = 0
elif deprecation_wng:
deprecated37("subtransactions are deprecated; instead of "
"transaction.commit(1), use "
"transaction.savepoint(optimistic=True) in "
"contexts where a subtransaction abort will never "
"occur, or sp=transaction.savepoint() if later "
"rollback is possible and then sp.rollback() "
"instead of transaction.abort(1)")
if self._savepoint2index: if self._savepoint2index:
self._invalidate_all_savepoints() self._invalidate_all_savepoints()
if subtransaction:
# TODO deprecate subtransactions
self._subtransaction_savepoint = self.savepoint(optimistic=True)
return
if self.status is Status.COMMITFAILED: if self.status is Status.COMMITFAILED:
self._prior_operation_failed() # doesn't return self._prior_operation_failed() # doesn't return
...@@ -546,28 +464,7 @@ class Transaction(object): ...@@ -546,28 +464,7 @@ class Transaction(object):
self.log.error("Error in tpc_abort() on manager %s", self.log.error("Error in tpc_abort() on manager %s",
rm, exc_info=sys.exc_info()) rm, exc_info=sys.exc_info())
def abort(self, subtransaction=_marker, deprecation_wng=True): def abort(self):
if subtransaction is _marker:
subtransaction = 0
elif deprecation_wng:
deprecated37("subtransactions are deprecated; use "
"sp.rollback() instead of "
"transaction.abort(1), where `sp` is the "
"corresponding savepoint captured earlier")
if subtransaction:
# TODO deprecate subtransactions.
if not self._subtransaction_savepoint:
raise interfaces.InvalidSavepointRollbackError
if self._subtransaction_savepoint.valid:
self._subtransaction_savepoint.rollback()
# We're supposed to be able to call abort(1) multiple
# times without additional effect, so mark the subtxn
# savepoint invalid now.
self._subtransaction_savepoint.transaction = None
assert not self._subtransaction_savepoint.valid
return
if self._savepoint2index: if self._savepoint2index:
self._invalidate_all_savepoints() self._invalidate_all_savepoints()
......
...@@ -202,8 +202,8 @@ class ITransaction(zope.interface.Interface): ...@@ -202,8 +202,8 @@ class ITransaction(zope.interface.Interface):
hooks. Applications should take care to avoid creating infinite loops hooks. Applications should take care to avoid creating infinite loops
by recursively registering hooks. by recursively registering hooks.
Hooks are called only for a top-level commit. A subtransaction Hooks are called only for a top-level commit. A savepoint
commit does not call any hooks. If the transaction is aborted, hooks does not call any hooks. If the transaction is aborted, hooks
are not called, and are discarded. Calling a hook "consumes" its are not called, and are discarded. Calling a hook "consumes" its
registration too: hook registrations do not persist across registration too: hook registrations do not persist across
transactions. If it's desired to call the same hook on every transactions. If it's desired to call the same hook on every
...@@ -231,8 +231,8 @@ class ITransaction(zope.interface.Interface): ...@@ -231,8 +231,8 @@ class ITransaction(zope.interface.Interface):
hooks. Applications should take care to avoid creating infinite loops hooks. Applications should take care to avoid creating infinite loops
by recursively registering hooks. by recursively registering hooks.
Hooks are called only for a top-level commit. A subtransaction Hooks are called only for a top-level commit. A
commit or savepoint creation does not call any hooks. If the savepoint creation does not call any hooks. If the
transaction is aborted, hooks are not called, and are discarded. transaction is aborted, hooks are not called, and are discarded.
Calling a hook "consumes" its registration too: hook registrations Calling a hook "consumes" its registration too: hook registrations
do not persist across transactions. If it's desired to call the same do not persist across transactions. If it's desired to call the same
...@@ -269,8 +269,8 @@ class ITransaction(zope.interface.Interface): ...@@ -269,8 +269,8 @@ class ITransaction(zope.interface.Interface):
hooks. Applications should take care to avoid creating infinite loops hooks. Applications should take care to avoid creating infinite loops
by recursively registering hooks. by recursively registering hooks.
Hooks are called only for a top-level commit. A subtransaction Hooks are called only for a top-level commit. A
commit or savepoint creation does not call any hooks. Calling a savepoint creation does not call any hooks. Calling a
hook "consumes" its registration: hook registrations do not hook "consumes" its registration: hook registrations do not
persist across transactions. If it's desired to call the same persist across transactions. If it's desired to call the same
hook on every transaction commit, then addAfterCommitHook() must be hook on every transaction commit, then addAfterCommitHook() must be
......
...@@ -46,12 +46,6 @@ import transaction ...@@ -46,12 +46,6 @@ import transaction
from ZODB.utils import positive_id from ZODB.utils import positive_id
from ZODB.tests.warnhook import WarningsHook from ZODB.tests.warnhook import WarningsHook
# deprecated37 remove when subtransactions go away
# Don't complain about subtxns in these tests.
warnings.filterwarnings("ignore",
".*\nsubtransactions are deprecated",
DeprecationWarning, __name__)
class TransactionTests(unittest.TestCase): class TransactionTests(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -114,37 +108,6 @@ class TransactionTests(unittest.TestCase): ...@@ -114,37 +108,6 @@ class TransactionTests(unittest.TestCase):
assert self.nosub1._p_jar.ctpc_finish == 0 assert self.nosub1._p_jar.ctpc_finish == 0
assert self.nosub1._p_jar.cabort == 1 assert self.nosub1._p_jar.cabort == 1
def BUGtestNSJSubTransactionCommitAbort(self):
"""
this reveals a bug in transaction.py
the nosub jar should not have tpc_finish
called on it till the containing txn
ends.
sub calling method commit
nosub calling method tpc_begin
sub calling method tpc_finish
nosub calling method tpc_finish
nosub calling method abort
sub calling method abort_sub
"""
self.sub1.modify(tracing='sub')
self.nosub1.modify(tracing='nosub')
self.transaction_manager.commit(1)
assert self.sub1._p_jar.ctpc_finish == 1
# bug, non sub trans jars are getting finished
# in a subtrans
assert self.nosub1._p_jar.ctpc_finish == 0
self.transaction_manager.abort()
assert self.nosub1._p_jar.cabort == 1
assert self.sub1._p_jar.cabort_sub == 1
### Failure Mode Tests ### Failure Mode Tests
# #
...@@ -158,7 +121,7 @@ class TransactionTests(unittest.TestCase): ...@@ -158,7 +121,7 @@ class TransactionTests(unittest.TestCase):
def testExceptionInAbort(self): def testExceptionInAbort(self):
self.sub1._p_jar = SubTransactionJar(errors='abort') self.sub1._p_jar = BasicJar(errors='abort')
self.nosub1.modify() self.nosub1.modify()
self.sub1.modify(nojar=1) self.sub1.modify(nojar=1)
...@@ -173,7 +136,7 @@ class TransactionTests(unittest.TestCase): ...@@ -173,7 +136,7 @@ class TransactionTests(unittest.TestCase):
def testExceptionInCommit(self): def testExceptionInCommit(self):
self.sub1._p_jar = SubTransactionJar(errors='commit') self.sub1._p_jar = BasicJar(errors='commit')
self.nosub1.modify() self.nosub1.modify()
self.sub1.modify(nojar=1) self.sub1.modify(nojar=1)
...@@ -188,7 +151,7 @@ class TransactionTests(unittest.TestCase): ...@@ -188,7 +151,7 @@ class TransactionTests(unittest.TestCase):
def testExceptionInTpcVote(self): def testExceptionInTpcVote(self):
self.sub1._p_jar = SubTransactionJar(errors='tpc_vote') self.sub1._p_jar = BasicJar(errors='tpc_vote')
self.nosub1.modify() self.nosub1.modify()
self.sub1.modify(nojar=1) self.sub1.modify(nojar=1)
...@@ -214,7 +177,7 @@ class TransactionTests(unittest.TestCase): ...@@ -214,7 +177,7 @@ class TransactionTests(unittest.TestCase):
sub calling method tpc_abort sub calling method tpc_abort
nosub calling method tpc_abort nosub calling method tpc_abort
""" """
self.sub1._p_jar = SubTransactionJar(errors='tpc_begin') self.sub1._p_jar = BasicJar(errors='tpc_begin')
self.nosub1.modify() self.nosub1.modify()
self.sub1.modify(nojar=1) self.sub1.modify(nojar=1)
...@@ -228,8 +191,7 @@ class TransactionTests(unittest.TestCase): ...@@ -228,8 +191,7 @@ class TransactionTests(unittest.TestCase):
assert self.sub1._p_jar.ctpc_abort == 1 assert self.sub1._p_jar.ctpc_abort == 1
def testExceptionInTpcAbort(self): def testExceptionInTpcAbort(self):
self.sub1._p_jar = SubTransactionJar( self.sub1._p_jar = BasicJar(errors=('tpc_abort', 'tpc_vote'))
errors=('tpc_abort', 'tpc_vote'))
self.nosub1.modify() self.nosub1.modify()
self.sub1.modify(nojar=1) self.sub1.modify(nojar=1)
...@@ -283,9 +245,9 @@ class DataObject: ...@@ -283,9 +245,9 @@ class DataObject:
def modify(self, nojar=0, tracing=0): def modify(self, nojar=0, tracing=0):
if not nojar: if not nojar:
if self.nost: if self.nost:
self._p_jar = NoSubTransactionJar(tracing=tracing) self._p_jar = BasicJar(tracing=tracing)
else: else:
self._p_jar = SubTransactionJar(tracing=tracing) self._p_jar = BasicJar(tracing=tracing)
self.transaction_manager.get().join(self._p_jar) self.transaction_manager.get().join(self._p_jar)
class TestTxnException(Exception): class TestTxnException(Exception):
...@@ -350,19 +312,6 @@ class BasicJar: ...@@ -350,19 +312,6 @@ class BasicJar:
self.check('tpc_finish') self.check('tpc_finish')
self.ctpc_finish += 1 self.ctpc_finish += 1
class SubTransactionJar(BasicJar):
def abort_sub(self, txn):
self.check('abort_sub')
self.cabort_sub = 1
def commit_sub(self, txn):
self.check('commit_sub')
self.ccommit_sub = 1
class NoSubTransactionJar(BasicJar):
pass
class HoserJar(BasicJar): class HoserJar(BasicJar):
# The HoserJars coordinate their actions via the class variable # The HoserJars coordinate their actions via the class variable
...@@ -482,17 +431,13 @@ def test_beforeCommitHook(): ...@@ -482,17 +431,13 @@ def test_beforeCommitHook():
>>> log >>> log
[] []
The hook is only called for a full commit, not for a savepoint or The hook is only called for a full commit, not for a savepoint.
subtransaction.
>>> t = transaction.begin() >>> t = transaction.begin()
>>> t.beforeCommitHook(hook, 'A', kw1='B') >>> t.beforeCommitHook(hook, 'A', kw1='B')
>>> dummy = t.savepoint() >>> dummy = t.savepoint()
>>> log >>> log
[] []
>>> t.commit(subtransaction=True)
>>> log
[]
>>> t.commit() >>> t.commit()
>>> log >>> log
["arg 'A' kw1 'B' kw2 'no_kw2'"] ["arg 'A' kw1 'B' kw2 'no_kw2'"]
...@@ -632,17 +577,13 @@ def test_addBeforeCommitHook(): ...@@ -632,17 +577,13 @@ def test_addBeforeCommitHook():
>>> log >>> log
[] []
The hook is only called for a full commit, not for a savepoint or The hook is only called for a full commit, not for a savepoint.
subtransaction.
>>> t = transaction.begin() >>> t = transaction.begin()
>>> t.addBeforeCommitHook(hook, 'A', dict(kw1='B')) >>> t.addBeforeCommitHook(hook, 'A', dict(kw1='B'))
>>> dummy = t.savepoint() >>> dummy = t.savepoint()
>>> log >>> log
[] []
>>> t.commit(subtransaction=True)
>>> log
[]
>>> t.commit() >>> t.commit()
>>> log >>> log
["arg 'A' kw1 'B' kw2 'no_kw2'"] ["arg 'A' kw1 'B' kw2 'no_kw2'"]
...@@ -810,17 +751,13 @@ def test_addAfterCommitHook(): ...@@ -810,17 +751,13 @@ def test_addAfterCommitHook():
>>> log >>> log
[] []
The hook is only called after a full commit, not for a savepoint or The hook is only called after a full commit, not for a savepoint.
subtransaction.
>>> t = transaction.begin() >>> t = transaction.begin()
>>> t.addAfterCommitHook(hook, 'A', dict(kw1='B')) >>> t.addAfterCommitHook(hook, 'A', dict(kw1='B'))
>>> dummy = t.savepoint() >>> dummy = t.savepoint()
>>> log >>> log
[] []
>>> t.commit(subtransaction=True)
>>> log
[]
>>> t.commit() >>> t.commit()
>>> log >>> log
["True arg 'A' kw1 'B' kw2 'no_kw2'"] ["True arg 'A' kw1 'B' kw2 'no_kw2'"]
...@@ -844,7 +781,7 @@ def test_addAfterCommitHook(): ...@@ -844,7 +781,7 @@ def test_addAfterCommitHook():
>>> class CommitFailure(Exception): >>> class CommitFailure(Exception):
... pass ... pass
>>> class FailingDataManager: >>> class FailingDataManager:
... def tpc_begin(self, txn, sub=False): ... def tpc_begin(self, txn):
... raise CommitFailure ... raise CommitFailure
... def abort(self, txn): ... def abort(self, txn):
... pass ... pass
......
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