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)
            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.


Participants:
  T: ITransaction
  o1, o2, o3: some persistent objects
  C1, C2, C3: resource managers
  S1, S2: Transaction savepoint objects
  s11, s21, s22: resource-manager savepoints

Scenario
    """Rollback of a savepoint"""

        create T
        o1.modify()
            C1.regisiter(o1)
                T.join(C1)
        T.savepoint()
            C1.savepoint()
                return s11
            return S1 = Savepoint(T, [r11])
        o1.modify()
            C1.regisiter(o1)
        o2.modify()
            C2.regisiter(o2)
                T.join(C2)
        T.savepoint()
            C1.savepoint()
                return s21
            C2.savepoint()
                return s22
            return S2 = Savepoint(T, [r21, r22])
        o3.modify()
            C3.regisiter(o3)
                T.join(C3)
        S1.rollback()
            S2.rollback()
                T.discard()
                    C1.discard()
                    C2.discard()
                    C3.discard()
                        o3.invalidate()
            S2.discard()
                s21.discard() # roll back changes since previous, which is r11
                    C1.discard(s21)
                        o1.invalidate()
                        # truncates temporary storage to s21's position
                s22.discard() # roll back changes since previous, which is r11
                    C1.discard(s22)
                        o2.invalidate()
                        # truncates temporary storage to beginning, because
                        # s22 was the first savepoint.  (Perhaps conection
                        # savepoints record the log position before the 
                        # data were written, which is 0 in this case. 
        T.commit()
            C1.beforeCompletion(T)
            C2.beforeCompletion(T)
            C3.beforeCompletion(T)
            C1.tpc_begin(T)
                S1.tpc_begin(T)
            C2.tpc_begin(T)
            C3.tpc_begin(T)
            C1.commit(T)
                S1.store(1, ..., T)
            C2.commit(T)
            C3.commit(T)
            C1.tpc_vote(T)
                S1.tpc_vote(T)
            C2.tpc_vote(T)
            C3.tpc_vote(T)
            C1.tpc_finish(T)
                S1.tpc_finish(T, f) # f is a callback function, which arranges
                                   c# to call DB.invalidate (next)
                    DB.invalidate(tid, {1: 1}, C)
            TM.free(T)
            C1.afterCompletion(T)
                C1._flush_invalidations()
            C2.afterCompletion(T)
                C2._flush_invalidations()
            C3.afterCompletion(T)
                C3._flush_invalidations()

