[Christian Heimes]
> Today I stumbled over an unexpected behavior of savepoints. As far as I'm
> able to understand savepoints they mark a well defined state in the
> middle of a transaction.


> A savepoint is invalid if its transaction is committed

Right -- or aborted.

> or another savepoint is created.

No, that's not the intent.  Savepoints are intended to act like a stack.
Each time you make a savepoint, it (in effect) pushes the current state on
the stack.  You can roll back to any state on the stack, and doing so makes
all the savepoints "at and after" the one you rolled back to unusable, but
leaves the ones "before" it still usable.  Like so:

tree[1] = 1

tree[1] = 2
sp2 = transaction.savepoint() # stack has sp2

tree[1] = 3
sp3 = transaction.savepoint() # stack has sp3 on top, then sp2

tree[1] = 4
assert tree[1] == 4

sp3.rollback()  # stack reduced to just sp2
assert tree[1] == 3

tree[1] = 5
assert tree[1] == 5

sp2.rollback()  # can still use sp2; stack becomes empty then
assert tree[1] == 2

> Well nesting savepoints would be a nice feature but I can live w/o it.

Nesting is very much an intended use case.  If you don't think it works,
show some code (maybe there's a bug not provoked by the pattern above). 

> Something else strikes me. Why am I unable to roll back to the same
> savepoint multiple times?

Because that's how it works <wink>.  Maybe Jim can explain why quickly --
offhand I'm not sure.  I don't think it could be guessed from the interface

class InvalidSavepointRollbackError(Exception):
    """Attempt to rollback an invalid savepoint.

    A savepoint may be invalid because:

    - The surrounding transaction has committed or aborted.

    - An earlier savepoint in the same transaction has been rolled back.

The last line there reads like it's OK to roll back to the _same_ savepoint
multiple times (it's not earlier than itself ...).  But
transaction/savepoint.txt explicitly says (and tests that) you can't, so
it's intended behavior:

    Once a savepoint has been used, it can't be used again:

    >>> savepoint.rollback()
    >>> dm['bob-balance']

    >>> savepoint.rollback()
    Traceback (most recent call last):

OTOH, the transaction.Savepoint.rollback() implementation is a bit
schizophrenic:  it invalidates self, but leaves it in the transaction's
stack (for savepoints "after" self, it both invalidates them and removes
them from the transaction's stack).

> Pseudo code example
> >>> sp = transaction.savepoint()
> >>> dosomething()
> >>> sp.valid
> True
> >>> sp.rollback()
> >>> domore()
> >>> sp.valid
> False
> >>> sp.rollback()
> FunkyRollbackException
>  From my point of view I can't see a reason why the ZODB forbids a
> second rolback to the savepoint.

For more information about ZODB, see the ZODB Wiki:

ZODB-Dev mailing list  -  ZODB-Dev@zope.org

Reply via email to