/md/stm/journal.py

https://github.com/pombredanne/md · Python · 211 lines · 158 code · 46 blank · 7 comment · 31 complexity · 4dcf4088f98e1a9a3c6cac8ee06f1e30 MD5 · raw file

  1. from __future__ import absolute_import
  2. import copy, threading
  3. from ..prelude import *
  4. from .interfaces import Cursor, Journal, Memory, Change, CannotCommit
  5. from .log import log, weaklog
  6. __all__ = (
  7. 'memory', 'journal',
  8. 'readable_state', 'original_state', 'writable_state',
  9. 'change_state', 'copy_state', 'commit_transaction',
  10. 'change', 'Deleted', 'Inserted',
  11. 'good', 'verify_read', 'verify_write', 'unverified_write'
  12. )
  13. ### Generic Operations
  14. def readable_state(journal, cursor, *default):
  15. return good(journal.readable_state, cursor, *default)
  16. def original_state(journal, cursor, *default):
  17. return good(journal.original_state, cursor, *default)
  18. def writable_state(journal, cursor):
  19. return good(journal.writable_state, cursor)
  20. def change_state(method, what, *args, **kwargs):
  21. if isinstance(what, Cursor):
  22. method(what, *args, **kwargs)
  23. else:
  24. ## cache cursors in a list so the log can be modified.
  25. for cursor in list(what):
  26. method(cursor, *args, **kwargs)
  27. def commit_transaction(source, nested):
  28. if source is nested:
  29. raise RuntimeError("A journal can't be committed to itself.")
  30. source.commit_transaction(nested)
  31. ### Journals
  32. class change(namedtuple('changes', 'cursor orig state'), Change):
  33. pass
  34. class journal(Journal):
  35. LogType = log
  36. name = None
  37. source = None
  38. def __init__(self, name, source):
  39. self.name = name
  40. self.source = source
  41. self.read_log = self.LogType()
  42. self.write_log = self.LogType()
  43. def __repr__(self):
  44. return '<%s %s>' % (type(self).__name__, str(self))
  45. def __str__(self):
  46. return self.name
  47. def make_journal(self, name):
  48. return type(self)(name, self)
  49. def allocate(self, cursor, state):
  50. self.write_log.allocate(cursor, state)
  51. return cursor
  52. def readable_state(self, cursor):
  53. try:
  54. return self.write_log[cursor]
  55. except KeyError:
  56. return self.original_state(cursor)
  57. def original_state(self, cursor):
  58. try:
  59. return self.read_log[cursor]
  60. except KeyError:
  61. state = good(self.source.readable_state, cursor, Inserted)
  62. self.read_log[cursor] = state
  63. return state
  64. def writable_state(self, cursor):
  65. try:
  66. return self.write_log[cursor]
  67. except KeyError:
  68. state = copy_state(self.original_state(cursor))
  69. self.write_log[cursor] = state
  70. return state
  71. def delete_state(self, cursor):
  72. self.write_log[cursor] = Deleted
  73. def rollback_state(self, cursor):
  74. self.write_log.pop(cursor, None)
  75. def commit_transaction(self, trans):
  76. ## A journal is single-threaded; state can be blindly copied
  77. ## in.
  78. for (cursor, orig, state) in trans.changed():
  79. self._write_log[cursor] = state
  80. def original(self):
  81. return iter(self.read_log)
  82. def changed(self):
  83. return (
  84. change(k, get_state(self.read_log, k), v)
  85. for (k, v) in self.write_log
  86. )
  87. class memory(Memory):
  88. JournalType = journal
  89. LogType = weaklog
  90. name = None
  91. def __init__(self, name='*memory*', check_read=True, check_write=True):
  92. self.name = name
  93. self.write_lock = threading.RLock()
  94. self.check_read = check_read
  95. self.check_write = check_write
  96. self.mem = self.LogType()
  97. def __repr__(self):
  98. return '<%s %s>' % (type(self).__name__, str(self))
  99. def __str__(self):
  100. return self.name
  101. def make_journal(self, name):
  102. return self.JournalType(name, self)
  103. def allocate(self, cursor, state):
  104. self.mem.allocate(cursor, state)
  105. return cursor
  106. def readable_state(self, cursor):
  107. return self.mem[cursor]
  108. def commit_transaction(self, trans):
  109. with self.write_lock:
  110. self._read(trans.original())
  111. self._commit(self._write(trans.changed()))
  112. def _read(self, read):
  113. if self.check_read:
  114. verify_read(self.mem, read)
  115. def _write(self, changed):
  116. if self.check_write:
  117. return verify_write(self.mem, changed)
  118. else:
  119. return unverified_write(changed)
  120. def _commit(self, changed):
  121. for (cursor, state) in changed:
  122. if state is Deleted:
  123. self.mem.pop(cursor, None)
  124. else:
  125. self.mem[cursor] = state
  126. ### State
  127. copy_state = copy.deepcopy
  128. Inserted = sentinal('<inserted>')
  129. Deleted = sentinal('<deleted>')
  130. def good(method, cursor, *default):
  131. try:
  132. value = method(cursor)
  133. if not isinstance(value, Sentinal):
  134. return value
  135. except KeyError:
  136. value = Undefined
  137. if default:
  138. return default[0]
  139. raise ValueError(
  140. '%s object %s %r.' % (type(cursor).__name__, id(cursor), value)
  141. )
  142. ### Operations on Logs
  143. def get_state(log, cursor):
  144. return log.get(cursor, Inserted)
  145. def verify_read(log, read):
  146. conflicts = [(c, s) for (c, s) in read if log.get(c) != s]
  147. if conflicts:
  148. raise CannotCommit(conflicts)
  149. def verify_write(log, changed):
  150. changed, conflicts = partition_conflicts(log, changed)
  151. if conflicts:
  152. raise CannotCommit(conflicts)
  153. return changed
  154. def unverified_write(changed):
  155. return list((c, s) for (c, o, s) in changed)
  156. def partition_conflicts(log, changed):
  157. good = []; bad = []
  158. for (cursor, orig, state) in changed:
  159. current = get_state(log, cursor)
  160. (good if current is orig else bad).append((cursor, state))
  161. return good, bad