Commit 252350a3 authored by Tim Peters's avatar Tim Peters

Interface repairs, of many kinds.

parent 00b57dc5
...@@ -330,7 +330,7 @@ class Connection(ExportImport, object): ...@@ -330,7 +330,7 @@ class Connection(ExportImport, object):
# the savepoint, then they won't have _p_oid or _p_jar after # the savepoint, then they won't have _p_oid or _p_jar after
# they've been unadded. This will make the code in _abort # they've been unadded. This will make the code in _abort
# confused. # confused.
self._abort() self._abort()
...@@ -341,7 +341,7 @@ class Connection(ExportImport, object): ...@@ -341,7 +341,7 @@ class Connection(ExportImport, object):
def _abort(self): def _abort(self):
"""Abort a transaction and forget all changes.""" """Abort a transaction and forget all changes."""
for obj in self._registered_objects: for obj in self._registered_objects:
oid = obj._p_oid oid = obj._p_oid
assert oid is not None assert oid is not None
...@@ -444,7 +444,7 @@ class Connection(ExportImport, object): ...@@ -444,7 +444,7 @@ class Connection(ExportImport, object):
self._commit_savepoint(transaction) self._commit_savepoint(transaction)
# No need to call _commit since savepoint did. # No need to call _commit since savepoint did.
else: else:
self._commit(transaction) self._commit(transaction)
...@@ -575,7 +575,7 @@ class Connection(ExportImport, object): ...@@ -575,7 +575,7 @@ class Connection(ExportImport, object):
if self._savepoint_storage is not None: if self._savepoint_storage is not None:
self._abort_savepoint() self._abort_savepoint()
self._storage.tpc_abort(transaction) self._storage.tpc_abort(transaction)
# Note: If we invalidate a non-justifiable object (i.e. a # Note: If we invalidate a non-justifiable object (i.e. a
...@@ -626,11 +626,15 @@ class Connection(ExportImport, object): ...@@ -626,11 +626,15 @@ class Connection(ExportImport, object):
def tpc_finish(self, transaction): def tpc_finish(self, transaction):
"""Indicate confirmation that the transaction is done.""" """Indicate confirmation that the transaction is done."""
def callback(tid): def callback(tid):
d = {} d = dict.fromkeys(self._modified)
for oid in self._modified:
d[oid] = 1
self._db.invalidate(tid, d, self) self._db.invalidate(tid, d, self)
# It's important that the storage calls the passed function
# while it still has its lock. We don't want another thread
# to be able to read any updated data until we've had a chance
# to send an invalidation message to all of the other
# connections!
self._storage.tpc_finish(transaction, callback) self._storage.tpc_finish(transaction, callback)
self._tpc_cleanup() self._tpc_cleanup()
...@@ -653,7 +657,7 @@ class Connection(ExportImport, object): ...@@ -653,7 +657,7 @@ class Connection(ExportImport, object):
# Transaction-manager synchronization -- ISynchronizer # Transaction-manager synchronization -- ISynchronizer
########################################################################## ##########################################################################
########################################################################## ##########################################################################
# persistent.interfaces.IPersistentDatamanager # persistent.interfaces.IPersistentDatamanager
...@@ -815,7 +819,7 @@ class Connection(ExportImport, object): ...@@ -815,7 +819,7 @@ class Connection(ExportImport, object):
# registering the object, because joining may take a # registering the object, because joining may take a
# savepoint, and the savepoint should not reflect the change # savepoint, and the savepoint should not reflect the change
# to the object. # to the object.
if self._needs_to_join: if self._needs_to_join:
self._txn_mgr.get().join(self) self._txn_mgr.get().join(self)
self._needs_to_join = False self._needs_to_join = False
...@@ -823,7 +827,7 @@ class Connection(ExportImport, object): ...@@ -823,7 +827,7 @@ class Connection(ExportImport, object):
if obj is not None: if obj is not None:
self._registered_objects.append(obj) self._registered_objects.append(obj)
# persistent.interfaces.IPersistentDatamanager # persistent.interfaces.IPersistentDatamanager
########################################################################## ##########################################################################
...@@ -1076,11 +1080,11 @@ class TmpStore: ...@@ -1076,11 +1080,11 @@ class TmpStore:
def __init__(self, base_version, storage): def __init__(self, base_version, storage):
self._storage = storage self._storage = storage
for method in ( for method in (
'getName', 'new_oid', 'modifiedInVersion', 'getSize', 'getName', 'new_oid', 'modifiedInVersion', 'getSize',
'undoLog', 'versionEmpty', 'sortKey', 'undoLog', 'versionEmpty', 'sortKey',
): ):
setattr(self, method, getattr(storage, method)) setattr(self, method, getattr(storage, method))
self._base_version = base_version self._base_version = base_version
self._file = tempfile.TemporaryFile() self._file = tempfile.TemporaryFile()
# position: current file position # position: current file position
...@@ -1089,7 +1093,7 @@ class TmpStore: ...@@ -1089,7 +1093,7 @@ class TmpStore:
# index: map oid to pos of last committed version # index: map oid to pos of last committed version
self.index = {} self.index = {}
self.creating = [] self.creating = []
def __len__(self): def __len__(self):
return len(self.index) return len(self.index)
......
...@@ -71,7 +71,8 @@ class ITransaction(zope.interface.Interface): ...@@ -71,7 +71,8 @@ class ITransaction(zope.interface.Interface):
Objects with this interface may represent different transactions Objects with this interface may represent different transactions
during their lifetime (.begin() can be called to start a new during their lifetime (.begin() can be called to start a new
transaction using the same instance). transaction using the same instance, although that example is
deprecated and will go away in ZODB 3.6).
""" """
user = zope.interface.Attribute( user = zope.interface.Attribute(
...@@ -123,29 +124,21 @@ class ITransaction(zope.interface.Interface): ...@@ -123,29 +124,21 @@ class ITransaction(zope.interface.Interface):
""" """
def join(datamanager): def join(datamanager):
"""Add a datamanager to the transaction. """Add a data manager to the transaction.
If the data manager supports savepoints, it must call join *before* `datamanager` must provide the transactions.interfaces.IDataManager
making any changes: if the transaction has made any savepoints, then interface.
the transaction will take a savepoint of the data manager when join
is called, and this savepoint must reflect the state of the data
manager before any changes that caused the data manager to join the
transaction.
The datamanager must implement the
transactions.interfaces.IDataManager interface, and be
adaptable to ZODB.interfaces.IDataManager.
""" """
def note(text): def note(text):
"""Add text to the transaction description. """Add text to the transaction description.
If a description has already been set, text is added to the This modifies the `.description` attribute; see its docs for more
end of the description following two newline characters. detail. First surrounding whitespace is stripped from `text`. If
Surrounding whitespace is stripped from text. `.description` is currently an empty string, then the stripped text
becomes its value, else two newlines and the stripped text are
appended to `.description`.
""" """
# Unsure: does impl do the right thing with ''? Not clear what
# the "right thing" is.
def setUser(user_name, path="/"): def setUser(user_name, path="/"):
"""Set the user name. """Set the user name.
...@@ -153,19 +146,23 @@ class ITransaction(zope.interface.Interface): ...@@ -153,19 +146,23 @@ class ITransaction(zope.interface.Interface):
path should be provided if needed to further qualify the path should be provided if needed to further qualify the
identified user. This is a convenience method used by Zope. identified user. This is a convenience method used by Zope.
It sets the .user attribute to str(path) + " " + str(user_name). It sets the .user attribute to str(path) + " " + str(user_name).
This sets the `.user` attribute; see its docs for more detail.
""" """
def setExtendedInfo(name, value): def setExtendedInfo(name, value):
"""Add extension data to the transaction. """Add extension data to the transaction.
name is the name of the extension property to set; value must name is the name of the extension property to set, of Python type
be a picklable value. str; value must be pickleable. Multiple calls may be made to set
multiple extension properties, provided the names are distinct.
Storage implementations may limit the amount of extension data Storages record the extension data, as meta-data, when a transaction
which can be stored. commits.
A storage may impose a limit on the size of extension data; behavior
is undefined if such a limit is exceeded (for example, a storage may
raise an exception, or remove `<name, value>` pairs).
""" """
# Unsure: is this allowed to cause an exception here, during
# the two-phase commit, or can it toss data silently?
def beforeCommitHook(hook, *args, **kws): def beforeCommitHook(hook, *args, **kws):
"""Register a hook to call before the transaction is committed. """Register a hook to call before the transaction is committed.
...@@ -195,7 +192,6 @@ class ITransaction(zope.interface.Interface): ...@@ -195,7 +192,6 @@ class ITransaction(zope.interface.Interface):
class ITransactionDeprecated(zope.interface.Interface): class ITransactionDeprecated(zope.interface.Interface):
"""Deprecated parts of the transaction API.""" """Deprecated parts of the transaction API."""
# TODO: deprecated36
def begin(info=None): def begin(info=None):
"""Begin a new transaction. """Begin a new transaction.
...@@ -207,6 +203,7 @@ class ITransactionDeprecated(zope.interface.Interface): ...@@ -207,6 +203,7 @@ class ITransactionDeprecated(zope.interface.Interface):
def register(object): def register(object):
"""Register the given object for transaction control.""" """Register the given object for transaction control."""
class IDataManager(zope.interface.Interface): class IDataManager(zope.interface.Interface):
"""Objects that manage transactional storage. """Objects that manage transactional storage.
...@@ -219,10 +216,6 @@ class IDataManager(zope.interface.Interface): ...@@ -219,10 +216,6 @@ class IDataManager(zope.interface.Interface):
the transaction. the transaction.
""" """
# Two-phase commit protocol. These methods are called by the
# ITransaction object associated with the transaction being
# committed.
def abort(transaction): def abort(transaction):
"""Abort a transaction and forget all changes. """Abort a transaction and forget all changes.
...@@ -232,6 +225,11 @@ class IDataManager(zope.interface.Interface): ...@@ -232,6 +225,11 @@ class IDataManager(zope.interface.Interface):
that are not yet in a two-phase commit. that are not yet in a two-phase commit.
""" """
# Two-phase commit protocol. These methods are called by the ITransaction
# object associated with the transaction being committed. The sequence
# of calls normally follows this regular expression:
# tpc_begin commit tpc_vote (tpc_finish | tpc_abort)
def tpc_begin(transaction): def tpc_begin(transaction):
"""Begin commit of a transaction, starting the two-phase commit. """Begin commit of a transaction, starting the two-phase commit.
...@@ -242,25 +240,13 @@ class IDataManager(zope.interface.Interface): ...@@ -242,25 +240,13 @@ class IDataManager(zope.interface.Interface):
def commit(transaction): def commit(transaction):
"""Commit modifications to registered objects. """Commit modifications to registered objects.
Save the object as part of the data to be made persistent if Save changes to be made persistent if the transaction commits (if
the transaction commits. tpc_finish is called later). If tpc_abort is called later, changes
must not persist.
This includes conflict detection and handling. If no conflicts or This includes conflict detection and handling. If no conflicts or
errors occur it saves the objects in the storage. errors occur, the data manager should be prepared to make the
""" changes persist when tpc_finish is called.
def tpc_abort(transaction):
"""Abort a transaction.
This is called by a transaction manager to end a two-phase commit on
the data manager.
This is always called after a tpc_begin call.
transaction is the ITransaction instance associated with the
transaction being committed.
This should never fail.
""" """
def tpc_vote(transaction): def tpc_vote(transaction):
...@@ -276,18 +262,27 @@ class IDataManager(zope.interface.Interface): ...@@ -276,18 +262,27 @@ class IDataManager(zope.interface.Interface):
def tpc_finish(transaction): def tpc_finish(transaction):
"""Indicate confirmation that the transaction is done. """Indicate confirmation that the transaction is done.
Make all changes to objects modified by this transaction persist.
transaction is the ITransaction instance associated with the transaction is the ITransaction instance associated with the
transaction being committed. transaction being committed.
This should never fail. If this raises an exception, the This should never fail. If this raises an exception, the
database is not expected to maintain consistency; it's a database is not expected to maintain consistency; it's a
serious error. serious error.
"""
def tpc_abort(transaction):
"""Abort a transaction.
It's important that the storage calls the passed function This is called by a transaction manager to end a two-phase commit on
while it still has its lock. We don't want another thread the data manager. Abandon all changes to objects modified by this
to be able to read any updated data until we've had a chance transaction.
to send an invalidation message to all of the other
connections! transaction is the ITransaction instance associated with the
transaction being committed.
This should never fail.
""" """
def sortKey(): def sortKey():
...@@ -321,7 +316,7 @@ class IDataManagerSavepoint(zope.interface.Interface): ...@@ -321,7 +316,7 @@ class IDataManagerSavepoint(zope.interface.Interface):
responsibility for, validity. It isn't the responsibility of responsibility for, validity. It isn't the responsibility of
data-manager savepoints to prevent multiple rollbacks or rollbacks after data-manager savepoints to prevent multiple rollbacks or rollbacks after
transaction termination. Preventing invalid savepoint rollback is the transaction termination. Preventing invalid savepoint rollback is the
responsibility of transaction rollbacks. Application code should never responsibility of transaction rollbacks. Application code should never
use data-manager savepoints. use data-manager savepoints.
""" """
......
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