Commit fc1ec799 authored by Tim Peters's avatar Tim Peters

Mostly clarifications of the complicated state transactions

are in now (partly ZODB 3, partly ZODB 4, partly transitional).
More is needed.
Added new collaborations.txt, which spells out some dynamics
of the system in a compact way.  This is in a format Jim used
for Zope 3, essentially a flat-text and less-formal representation
of UML sequence diagrams.
parent a04752da
...@@ -452,9 +452,10 @@ class Connection(ExportImport, object): ...@@ -452,9 +452,10 @@ class Connection(ExportImport, object):
self._cache = cache = PickleCache(self, cache_size) self._cache = cache = PickleCache(self, cache_size)
def abort(self, transaction): def abort(self, transaction):
"""Abort the object in the transaction. """Abort modifications to registered objects.
This just deactivates the thing. This tells the cache to invalidate changed objects. _p_jar
and _p_oid are deleted from new objects.
""" """
for obj in self._registered_objects: for obj in self._registered_objects:
...@@ -632,6 +633,7 @@ class Connection(ExportImport, object): ...@@ -632,6 +633,7 @@ class Connection(ExportImport, object):
self._cache[oid] = obj self._cache[oid] = obj
except: except:
# Dang, I bet it's wrapped: # Dang, I bet it's wrapped:
# XXX Deprecate, then remove, this.
if hasattr(obj, 'aq_base'): if hasattr(obj, 'aq_base'):
self._cache[oid] = obj.aq_base self._cache[oid] = obj.aq_base
else: else:
......
Participants
DB: ZODB.DB.DB
C: ZODB.Connection.Connection
S: ZODB.FileStorage.FileStorage
T: transaction.interfaces.ITransaction
TM: transaction.interfaces.ITransactionManager
o1, o2, ...: pre-existing persistent objects
Scenario
"""Simple fetch, modify, commit."""
DB.open()
create C
TM.registerSynch(C)
TM.begin()
create T
C.get(1) # fetches o1
C.get(2) # fetches o2
C.get(3) # fetches o3
o1.modify() # anything that modifies o1
C.register(o1)
T.join(C)
o2.modify()
C.register(o2)
# T.join(C) does not happen again
o1.modify()
# C.register(o1) doesn't happen again, because o1 was already
# in the changed state.
T.commit()
C.beforeCompletion(T)
C.tpc_begin(T, False)
S.tpc_begin(T)
C.commit(T)
S.store(1, ..., T)
S.store(2, ..., T)
# o3 is not stored, because it wasn't modified
C.tpc_vote(T)
S.tpc_vote(T)
C.tpc_finish(T)
S.tpc_finish(T, f) # f is a callback function, which arranges
# to call DB.invalidate (next)
DB.invalidate(tid, {1: 1, 2: 1}, C)
C2.invalidate(tid, {1: 1, 2: 1}) # for all connections
# C2 to DB, where C2
# is not C
TM.free(T)
C.afterCompletion(T)
C._flush_invalidations()
# Processes invalidations that may have come in from other
# transactions.
Participants
DB: ZODB.DB.DB
C: ZODB.Connection.Connection
S: ZODB.FileStorage.FileStorage
T: transaction.interfaces.ITransaction
TM: transaction.interfaces.ITransactionManager
o1, o2, ...: pre-existing persistent objects
Scenario
"""Simple fetch, modify, abort."""
DB.open()
create C
TM.registerSynch(C)
TM.begin()
create T
C.get(1) # fetches o1
C.get(2) # fetches o2
C.get(3) # fetches o3
o1.modify() # anything that modifies o1
C.register(o1)
T.join(C)
o2.modify()
C.register(o2)
# T.join(C) does not happen again
o1.modify()
# C.register(o1) doesn't happen again, because o1 was already
# in the changed state.
T.abort()
C.beforeCompletion(T)
C.abort(T)
C._cache.invalidate(1) # toss changes to o1
C._cache.invalidate(2) # toss changes to o2
# o3 wasn't modified, and its cache entry isn't invalidated.
TM.free(T)
C.afterCompletion(T)
C._flush_invalidations()
# Processes invalidations that may have come in from other
# transactions.
...@@ -17,204 +17,6 @@ $Id$ ...@@ -17,204 +17,6 @@ $Id$
""" """
import zope.interface import zope.interface
from zope.interface import Attribute
class IDataManager(zope.interface.Interface):
"""Objects that manage transactional storage.
These object's may manage data for other objects, or they may manage
non-object storages, such as relational databases.
"""
def abort_sub(transaction):
"""Discard all subtransaction data.
See subtransaction.txt
This is called when top-level transactions are aborted.
No further subtransactions can be started once abort_sub()
has been called; this is only used when the transaction is
being aborted.
abort_sub also implies the abort of a 2-phase commit.
This should never fail.
"""
def commit_sub(transaction):
"""Commit all changes made in subtransactions and begin 2-phase commit
Data are saved *as if* they are part of the current transaction.
That is, they will not be persistent unless the current transaction
is committed.
This is called when the current top-level transaction is committed.
No further subtransactions can be started once commit_sub()
has been called; this is only used when the transaction is
being committed.
This call also implied the beginning of 2-phase commit.
"""
# Two-phase commit protocol. These methods are called by the
# ITransaction object associated with the transaction being
# committed.
def tpc_begin(transaction, subtransaction=False):
"""Begin commit of a transaction, starting the two-phase commit.
transaction is the ITransaction instance associated with the
transaction being committed.
subtransaction is a Boolean flag indicating whether the
two-phase commit is being invoked for a subtransaction.
Important note: Subtransactions are modelled in the sense that
when you commit a subtransaction, subsequent commits should be
for subtransactions as well. That is, there must be a
commit_sub() call between a tpc_begin() call with the
subtransaction flag set to true and a tpc_begin() with the
flag set to false.
"""
def tpc_abort(transaction):
"""Abort a transaction.
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_finish(transaction):
"""Indicate confirmation that the transaction is done.
transaction is the ITransaction instance associated with the
transaction being committed.
This should never fail. If this raises an exception, the
database is not expected to maintain consistency; it's a
serious error.
"""
def tpc_vote(transaction):
"""Verify that a data manager can commit the transaction
This is the last chance for a data manager to vote 'no'. A
data manager votes 'no' by raising an exception.
transaction is the ITransaction instance associated with the
transaction being committed.
"""
def commit(object, transaction):
"""CCCommit changes to an object
Save the object as part of the data to be made persistent if
the transaction commits.
"""
def abort(object, transaction):
"""Abort changes to an object
Only changes made since the last transaction or
sub-transaction boundary are discarded.
This method may be called either:
o Outside of two-phase commit, or
o In the first phase of two-phase commit
"""
class ITransaction(zope.interface.Interface):
"""Object representing a running transaction.
Objects with this interface may represent different transactions
during their lifetime (.begin() can be called to start a new
transaction using the same instance).
"""
user = Attribute(
"user",
"The name of the user on whose behalf the transaction is being\n"
"performed. The format of the user name is defined by the\n"
"application.")
# XXX required to be a string?
description = Attribute(
"description",
"Textual description of the transaction.")
def begin(info=None, subtransaction=None):
"""Begin a new transaction.
If the transaction is in progress, it is aborted and a new
transaction is started using the same transaction object.
"""
def commit(subtransaction=None):
"""Finalize the transaction.
This executes the two-phase commit algorithm for all
IDataManager objects associated with the transaction.
"""
def abort(subtransaction=0, freeme=1):
"""Abort the transaction.
This is called from the application. This can only be called
before the two-phase commit protocol has been started.
"""
def join(datamanager):
"""Add a datamanager to the transaction.
The datamanager must implement the
transactions.interfaces.IDataManager interface, and be
adaptable to ZODB.interfaces.IDataManager.
"""
def register(object):
"""Register the given object for transaction control."""
def note(text):
"""Add text to the transaction description.
If a description has already been set, text is added to the
end of the description following two newline characters.
Surrounding whitespace is stripped from text.
"""
# XXX does impl do the right thing with ''? Not clear what
# the "right thing" is.
def setUser(user_name, path="/"):
"""Set the user name.
path should be provided if needed to further qualify the
identified user.
"""
def setExtendedInfo(name, value):
"""Add extension data to the transaction.
name is the name of the extension property to set; value must
be a picklable value.
Storage implementations may limit the amount of extension data
which can be stored.
"""
# XXX is this this allowed to cause an exception here, during
# the two-phase commit, or can it toss data silently?
class IConnection(zope.interface.Interface): class IConnection(zope.interface.Interface):
"""ZODB connection. """ZODB connection.
......
...@@ -16,19 +16,137 @@ ...@@ -16,19 +16,137 @@
$Id$ $Id$
""" """
from zope.interface import Interface import zope.interface
class IDataManager(Interface): class IDataManagerOriginal(zope.interface.Interface):
"""Data management interface for storing objects transactionally """Objects that manage transactional storage.
This is currently implemented by ZODB database connections. These objects may manage data for other objects, or they may manage
non-object storages, such as relational databases.
XXX This exists to document ZODB4 behavior, to help create some IDataManagerOriginal is the interface currently provided by ZODB
backward-compatability support for Zope 3. New classes shouldn't database connections, but the intent is to move to the newer
implement this. They should implement ZODB.interfaces.IDataManager IDataManager.
for now. Our hope is that there will eventually be an interface """
like this or that this interface will evolve and become the
standard interface. There are some issues to be resolved first, like: def abort_sub(transaction):
"""Discard all subtransaction data.
See subtransaction.txt
This is called when top-level transactions are aborted.
No further subtransactions can be started once abort_sub()
has been called; this is only used when the transaction is
being aborted.
abort_sub also implies the abort of a 2-phase commit.
This should never fail.
"""
def commit_sub(transaction):
"""Commit all changes made in subtransactions and begin 2-phase commit
Data are saved *as if* they are part of the current transaction.
That is, they will not be persistent unless the current transaction
is committed.
This is called when the current top-level transaction is committed.
No further subtransactions can be started once commit_sub()
has been called; this is only used when the transaction is
being committed.
This call also implied the beginning of 2-phase commit.
"""
# Two-phase commit protocol. These methods are called by the
# ITransaction object associated with the transaction being
# committed.
def tpc_begin(transaction, subtransaction=False):
"""Begin commit of a transaction, starting the two-phase commit.
transaction is the ITransaction instance associated with the
transaction being committed.
subtransaction is a Boolean flag indicating whether the
two-phase commit is being invoked for a subtransaction.
Important note: Subtransactions are modelled in the sense that
when you commit a subtransaction, subsequent commits should be
for subtransactions as well. That is, there must be a
commit_sub() call between a tpc_begin() call with the
subtransaction flag set to true and a tpc_begin() with the
flag set to false.
"""
def tpc_abort(transaction):
"""Abort a transaction.
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_finish(transaction):
"""Indicate confirmation that the transaction is done.
transaction is the ITransaction instance associated with the
transaction being committed.
This should never fail. If this raises an exception, the
database is not expected to maintain consistency; it's a
serious error.
"""
def tpc_vote(transaction):
"""Verify that a data manager can commit the transaction
This is the last chance for a data manager to vote 'no'. A
data manager votes 'no' by raising an exception.
transaction is the ITransaction instance associated with the
transaction being committed.
"""
def commit(object, transaction):
"""CCCommit changes to an object
Save the object as part of the data to be made persistent if
the transaction commits.
"""
def abort(object, transaction):
"""Abort changes to an object
Only changes made since the last transaction or
sub-transaction boundary are discarded.
This method may be called either:
o Outside of two-phase commit, or
o In the first phase of two-phase commit
"""
class IDataManager(zope.interface.Interface):
"""Data management interface for storing objects transactionally.
ZODB database connections currently provides the older
IDataManagerOriginal interface, but the intent is to move to this newer
IDataManager interface.
Our hope is that this interface will evolve and become the standard
interface. There are some issues to be resolved first, like:
- Probably want separate abort methods for use in and out of - Probably want separate abort methods for use in and out of
two-phase commit. two-phase commit.
...@@ -99,8 +217,87 @@ class IDataManager(Interface): ...@@ -99,8 +217,87 @@ class IDataManager(Interface):
""" """
class ITransaction(zope.interface.Interface):
"""Object representing a running transaction.
Objects with this interface may represent different transactions
during their lifetime (.begin() can be called to start a new
transaction using the same instance).
"""
user = zope.interface.Attribute(
"user",
"The name of the user on whose behalf the transaction is being\n"
"performed. The format of the user name is defined by the\n"
"application.")
# XXX required to be a string?
description = zope.interface.Attribute(
"description",
"Textual description of the transaction.")
def begin(info=None, subtransaction=None):
"""Begin a new transaction.
If the transaction is in progress, it is aborted and a new
transaction is started using the same transaction object.
"""
def commit(subtransaction=None):
"""Finalize the transaction.
This executes the two-phase commit algorithm for all
IDataManager objects associated with the transaction.
"""
def abort(subtransaction=0, freeme=1):
"""Abort the transaction.
This is called from the application. This can only be called
before the two-phase commit protocol has been started.
"""
def join(datamanager):
"""Add a datamanager to the transaction.
The datamanager must implement the
transactions.interfaces.IDataManager interface, and be
adaptable to ZODB.interfaces.IDataManager.
"""
def register(object):
"""Register the given object for transaction control."""
def note(text):
"""Add text to the transaction description.
If a description has already been set, text is added to the
end of the description following two newline characters.
Surrounding whitespace is stripped from text.
"""
# XXX does impl do the right thing with ''? Not clear what
# the "right thing" is.
def setUser(user_name, path="/"):
"""Set the user name.
path should be provided if needed to further qualify the
identified user.
"""
def setExtendedInfo(name, value):
"""Add extension data to the transaction.
name is the name of the extension property to set; value must
be a picklable value.
Storage implementations may limit the amount of extension data
which can be stored.
"""
# XXX is this this allowed to cause an exception here, during
# the two-phase commit, or can it toss data silently?
class IRollback(Interface): class IRollback(zope.interface.Interface):
def rollback(): def rollback():
"""Rollback changes since savepoint. """Rollback changes since savepoint.
......
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