The stm module is a simple, optimistically concurrent transactional memory implementation. Transactions are designed to operate against a common memory and use copy-on-write to isolate changes in state from each other. Transactional data types participate by declaring how each of their methods will affect instance state.
>>> from md.stm import *
A transactional data type implements Cursor and is designed to cooperate with transactional memory by using four instance operators: readable(), writable(), allocate(), and delete(). Each operator declares how an instance’s state is about to be affected by a method. Classes implemented using these operators are called “cursors” because they act as fixed interfaces that operate against different transactional states depending on the context.
A simple Cursor implementation is provided by stm. It defines __new__(), __getattr__(), __setattr__(), and __delattr__(). Simply inherit from cursor instead of object.
>>> class cell(cursor):
... def __init__(self, value):
... self.value = value
...
... def __repr__(self):
... return '<cell %r>' % self.value
The type of state given to allocate() can be overridden by redefining the cursor.StateType attribute.
>>> class sequence(cursor):
... StateType = list
...
... def __init__(self, seq=()):
... self.extend(seq)
...
... def __repr__(self):
... return '<sequence %r>' % readable(self)
...
... def __getitem__(self, key):
... return readable(self)[key]
...
... def __setitem__(self, key, value):
... writable(self)[key] = value
...
... def __delitem__(self, key):
... del writable(self)[key]
...
... def extend(self, seq):
... writable(self).extend(seq)
This is a basic optimistic concurrency operator. It attempts to run proc(*args, **kwargs) inside a transaction several times before giving up. See Software Transactional Memory for examples. The transactionally() operator accepts three optional keyword arguments and returns the result of calling proc.
| Parameters: |
|
|---|
Transactions auto-commit and auto-save by default. Use save() to add changes that will be committed when auto-save is disabled or before calling a nested transaction. Unsaved changes are discarded when the transaction is completed. Without any arguments, all unsaved() changes are saved. Otherwise, what may be a cursor or sequence of cursors.
>>> with transaction(autosave=False):
... s1 = save(tlist([1, 2, 3]))
... c1 = save(cell(s1))
>>> c1.value
tlist([1, 2, 3])
>>> with transaction(autosave=False):
... c1.value[1] = 20
>>> c1.value
tlist([1, 2, 3])
Save must be called on the cursor that’s changed. Calling save on a cursor referring to a changed cursor won’t work.
>>> with transaction(autosave=False):
... c1.value[1] = 20
... save(c1.value)
tlist([1, 20, 3])
>>> c1.value
tlist([1, 20, 3])
>>> with transaction(autocommit=False, autosave=False):
... c1.value[2] = 30
... save(c1)
<cell tlist([1, 20, 30])>
>>> c1
<cell tlist([1, 20, 3])>
Leaving the autosave argument set to True is convenient for “always commit everything” transactions.
>>> with transaction():
... c2 = cell(tlist(['a', 'b', 'c']))
>>> c2.value
tlist(['a', 'b', 'c'])
Revert a cursor to its last saved state (the opposite of save()). When called with no arguments, all unsaved() cursors are reverted.
>>> with transaction(autosave=False):
... c2.value[0] = 'A'
... with transaction(autosave=False):
... print c2.value, '(nested)'
... c2.value[0] = 'Z'
... print c2.value, '(after nested; no save)'
... print rollback(c2.value), '(rollback)'
... c2.value[0] = 'Z'
... print save(c2.value), '(saved)'
... with transaction(autosave=False):
... print c2.value, '(nested2)'
... c2.value[1] = 'Y'
... print save(c2.value), '(nested2 save)'
... print c2.value, '(after nested2 save)'
tlist(['a', 'b', 'c']) (nested)
tlist(['A', 'b', 'c']) (after nested; no save)
tlist(['a', 'b', 'c']) (rollback)
tlist(['Z', 'b', 'c']) (saved)
tlist(['Z', 'b', 'c']) (nested2)
tlist(['Z', 'Y', 'c']) (nested2 save)
tlist(['Z', 'Y', 'c']) (after nested2 save)
Terminates the current transaction. Any uncommitted changes are discarded.
>>> with transaction():
... c3 = cell('apple')
... with transaction():
... c3.value = 'banana'
... abort()
>>> c3.value
'apple'
Produce an iterator over the items that need to be added to a transaction’s save-log.
>>> with transaction(autosave=False):
... c1.value[0] = 10
... c2.value[1] = 'B'
... print list(saved()), list(unsaved())
... save()
... print list(saved()), list(unsaved())
[] [tlist([10, 20, 3]), tlist(['Z', 'B', 'c'])]
[tlist([10, 20, 3]), tlist(['Z', 'B', 'c'])] []
>>> c2.value
tlist(['Z', 'B', 'c'])
A cursor does not have a built-in persistent identity; dumping and loading a cursor produces a copy. Subclasses of cursor may override __getstate__() to specialize the state that is reduced; by default readable(self) is returned. Pickling a cursor in the middle of a transaction could lead to unexpected results if the cursor is unsaved or the transaction is uncommitted.
See the examples in Software Transactional Memory for a simple persistent memory implementation.
>>> from cPickle import dumps, loads
>>> with transaction():
... o1 = cursor(); o2 = cursor()
... o1.foo = o2
... o2.bar = 1
>>> o3 = loads(dumps(o1, -1))
>>> o3 is not o1; o3.foo is not o2; o3.foo.bar
True
True
1