[h5py] 26/455: Base class for transactions
Ghislain Vaillant
ghisvail-guest at moszumanska.debian.org
Thu Jul 2 18:19:13 UTC 2015
This is an automated email from the git hooks/post-receive script.
ghisvail-guest pushed a commit to annotated tag 1.3.0
in repository h5py.
commit c36b05505699a6380397228e26b746595851d9f4
Author: andrewcollette <andrew.collette at gmail.com>
Date: Fri May 23 06:11:42 2008 +0000
Base class for transactions
---
h5py/highlevel.py | 117 ++++++++++++++++++++++++++++++
h5py/transactions.py | 197 +++++++++++++++++++++++++--------------------------
2 files changed, 213 insertions(+), 101 deletions(-)
diff --git a/h5py/highlevel.py b/h5py/highlevel.py
index fbbebca..b16dfe0 100644
--- a/h5py/highlevel.py
+++ b/h5py/highlevel.py
@@ -48,6 +48,7 @@ import cmd
import random
import string
import numpy
+import posixpath
import h5f
import h5g
@@ -58,6 +59,122 @@ import h5a
import h5p
from errors import H5Error
+from transactions import Action, TransactionManager, TransactionStateError
+
+# === Base classes / context manager support ==================================
+
+class NamedObject(object):
+
+ """ Base class for objects which reside in HDF5 files. Among other things,
+ any named object is a valid context manager for Python's "with"
+ statement, capable of tracking transactions in HDF5 files.
+ """
+
+ def __init__(self):
+ self.manager = None
+ self._auto = False
+
+ def __enter__(self):
+ """ Put the object in transaction mode. If no transaction manager is
+ currently associated with the object, create one.
+
+ Please don't call this manually.
+ """
+ stat = h5g.get_objinfo(self.id, '.')
+ token = (stat.fileno, stat.objno)
+
+ if self.manager is None:
+ self.manager = TransactionManager()
+
+ self.manager.lock(token)
+ self._auto = True
+ return self.manager
+
+ def __exit__(self, type_, value, tb):
+ """ Exit transaction mode.
+
+ Please don't call this manually.
+ """
+ if type_ is None:
+ self.manager.commit()
+ else:
+ self.manager.rollback()
+
+ stat = h5g.get_objinfo(self.id, '.')
+ token = (stat.fileno, stat.objno)
+
+ self.manager.unlock(token)
+ self.manager = None
+ self._auto = False
+
+ def begin_transaction(self, manager=None):
+ """ Manually put the object into "transaction" mode. Every API call
+ which can affect the underlying HDF5 state is recorded, and can
+ be reversed. If the object is already in transaction mode, raises
+ TransactionStateError.
+
+ The return value is a TransactionManager instance, which provides
+ methods like commit(), rollback(), etc.
+
+ You can optionally use an existing transaction manager. This lets
+ you track changes across multiple objects, with automatic
+ interlocking to prevent double access to HDF5 entities through
+ multiple Python objects.
+
+ As an alternative to manually beginning and ending transactions,
+ you can use any object of this class as the context manager in a
+ Python "with" statement.
+ """
+ if self._auto:
+ raise TransactionStateError('No manual control inside a "with" block.')
+
+ if self.manager is not None:
+ raise TransactionStateError("Transaction already in progress.")
+
+ self.manager = manager
+ return self.__enter__()
+
+ def end_transaction(self):
+ """ Take the object out of "transaction" mode. Implicitly commits any
+ pending actions. Raises TransactionStateError if the object is not
+ in transaction mode.
+ """
+ if self._auto:
+ raise TransactionStateError('No manual control inside a "with" block.')
+ if self.manager is None:
+ raise TransactionStateError("No transaction in progress.")
+
+ self.__exit__(None, None, None)
+
+class WithWrapper(object):
+
+ """ Create a single context manager out of many named objects, all sharing
+ the same transaction manager.
+ """
+
+ def __init__(self, *objs):
+ self.objs = objs
+
+ def __enter__(self):
+ mgr = TransactionManger
+
+ for obj in objs:
+ obj.manager = mgr
+ obj.__enter__()
+
+ return mgr
+
+ def __exit__(self, type_, value, tb):
+ return any(obj.__exit__(type_, value, tb) for obj in objs)
+
+def many(*args):
+ """ Enables tracking of multiple named objects in Python's "with" statement.
+ with_many(obj1, obj2, ...)
+ """
+ c_mgr = WithWrapper(*args)
+ return c_mgr
+
+
# === Main classes (Dataset/Group/File) =======================================
class Dataset(object):
diff --git a/h5py/transactions.py b/h5py/transactions.py
index fd5a170..323891b 100644
--- a/h5py/transactions.py
+++ b/h5py/transactions.py
@@ -6,17 +6,7 @@ class TransactionError(StandardError):
pass
class TransactionStateError(TransactionError):
- """ Illegal state change inside an Action object.
- """
- pass
-
-class NotTransactableError(TransactionError):
- """ A non-transactable operation was attempted while in transaction mode.
- """
- pass
-
-class DeadTransactionError(TransactionError):
- """ A transactable operation was attempted while not in transaction mode.
+ """ Attempted an operation which doesn't make sense in the current context.
"""
pass
@@ -60,6 +50,9 @@ class Action(object):
undo: Completely reverse the effects of the "do" operation.
commit: Clean up any temporary data created by the "do" operation.
+ Any return value from the callable is ignored. It must not raise
+ an exception.
+
"""
self.name = name
self._validate(do, 'do')
@@ -106,68 +99,114 @@ class Action(object):
class TransactionManager(object):
- active = property(lambda self: self.stack is not None)
+ """ Provides locking and transaction support, via a stack of Action objects.
+
+ Objects of this class are designed to manage access to a set of
+ resources, manipulated by calling functions or bound methods. It
+ implements two conceptually different interfaces:
+
+ 1. Locking
+ Since a single transaction manager can process commands affecting a
+ variety of objects, you can "lock" an object by providing an identifing
+ token (which could even be the object itself) via the function lock().
+ Attempting to lock an object twice will raise ValueError.
+
+ 2. Transactions
+ Transactions are implemented via a stack of Action objects. Add actions
+ to the manager via do(). Each action is immediately performed when
+ this method is called. At any time you can call commit(), to purge the
+ stack, or rollback() to reverse all changes up to the last commit().
+ Additionally, by calling undo() and redo(), you can manually walk the
+ object through the entire stack of transaction states.
+
+ """
+
+ # Pointer states:
+ # None: uninitialized
+ # 0: off the bottom of the stack
+ # <double>: index of an action on the stack
def __init__(self, max_size=None):
- """ Create a new transaction manager, inactive by default.
+ """ Create a new transaction manager. The optional max_size keyword
+ indicates the maximum allowed stack size. When this limit is
+ reached, the oldest action is committed and discarded when a new
+ action is added.
"""
- self.stack = None
+ self.stack = {}
+ self.locks = set()
self.ptr = None
- if max_size < 1:
+ if max_size is not None and max_size < 1:
raise ValueError("Stack size must be at least 1 (got %d)" % max_size)
self.max_size = max_size
- def _check(self):
- if not self.active:
- raise DeadTransactionError("No current transaction.")
+ # --- Locking code --------------------------------------------------------
- def begin(self):
- """ Begin a new transaction. Implicitly commits any pending actions.
+ def is_locked(self, item):
+ """ Determine if this resource is currently locked.
"""
- if self.active:
- self.commit()
- self.stack = {}
- self.ptr = None
+ return item in self.locks
+
+ def lock(self, item):
+ """ Lock a resource. Raises ValueError if it's already locked.
+ """
+ if item in self.locks:
+ raise ValueError('%s is already locked for transactions.' % str(item))
+ self.locks.add(item)
+
+ def unlock(self, item):
+ """ Release a resource. Raises ValueError if it's already locked.
+ """
+ if not item in self.locks:
+ raise ValueError("%s is not locked for transactions." % item)
+ self.locks.remove(item)
+
+ # --- Transactions code ---------------------------------------------------
def commit(self):
- """ Commit every action which is in the "done" state, and destroy
+ """ Commit every action which is in the "done" state, and reset
the stack.
"""
- self._check()
for t in sorted(self.stack):
action = self.stack[t]
if action.state == Action.DONE:
action.commit()
- self.stack = None
+ self.stack = {}
self.ptr = None
def rollback(self):
- """ Undo every action which is in the "done" state, and destroy
+ """ Undo every action which is in the "done" state, and reset
the stack.
"""
- self._check()
for t in sorted(self.stack, reverse=True):
action = self.stack[t]
if action.state == Action.DONE:
action.undo()
- self.stack = None
+ self.stack = {}
self.ptr = None
- def at_top(self):
- """ Check if the pointer is at the top of the stack.
+ def can_redo(self):
+ """ Determine if the stack/pointer system is capable of redoing the
+ next action on the stack. Fails if the pointer is None or at
+ the top of the stack.
"""
- self._check()
- if len(self.stack) == 0 or self.ptr == max(self.stack):
- return True
- return False
+ if self.ptr is None:
+ return False
+ assert self.ptr in self.stack or self.ptr == 0
- def at_bottom(self):
- """ Check if the pointer is at the bottom of the stack.
+ if self.ptr == max(self.stack):
+ return False
+ return True
+
+ def can_undo(self):
+ """ Determine if the stack/pointer system is capable of undoing the
+ action currently pointed at. Fails if the pointer is None, or
+ off the bottom of the stack.
"""
- self._check()
- if len(self.stack) == 0 or self.ptr == min(self.stack):
- return True
- return False
+ if self.ptr is None or self.ptr == 0:
+ return False
+ assert self.ptr in self.stack
+
+ return True
def do(self, action):
""" Perform the given action and add it to the stack. Implicitly
@@ -176,16 +215,17 @@ class TransactionManager(object):
max_size, commit and discard the oldest action.
The action's do() method is called before any modification is
- made to the stack.
+ made to the stack; if it raises an exception this won't trash
+ the object.
"""
- self._check()
action.do()
- if self.ptr is not None:
- for t in sorted(self.stack):
- if t > self.ptr:
- assert self.stack[t].state == Action.READY
- del self.stack[t]
+ assert len(self.stack) == 0 or self.ptr in self.stack
+
+ for t in sorted(self.stack):
+ if t > self.ptr:
+ assert self.stack[t].state == Action.READY
+ del self.stack[t]
key = time.time()
self.stack[key] = action
@@ -199,26 +239,24 @@ class TransactionManager(object):
def undo(self):
""" Undo the action targeted by the current pointer, and move the
pointer down one level on the stack. Does nothing if the pointer
- is None or off the bottom of the stack.
+ system isn't ready for an Undo.
"""
- self._check()
- if self.ptr is not None and self.ptr >= 0:
+ if self.can_undo():
self.stack[self.ptr].undo()
keys = sorted(self.stack)
idx = keys.index(self.ptr)
- if self.at_bottom():
- self.ptr = -1
+ if idx == 0:
+ self.ptr = 0
else:
self.ptr = keys[idx-1]
def redo(self):
""" Increment the pointer and redo the resulting action. Does nothing
- if the pointer is already at the top of the stack, or is None.
+ if the pointer system isn't ready for a Redo.
"""
- self._check()
- if not self.at_top() and self.ptr is not None:
+ if self.can_redo():
keys = sorted(self.stack)
- if self.ptr < 0:
+ if self.ptr == 0:
self.ptr = min(keys)
else:
idx = keys.index(self.ptr)
@@ -227,49 +265,6 @@ class TransactionManager(object):
-class LockManger(object):
-
- """
- Trivial locking class.
- """
-
- def __init__(self):
- self.locks = set()
-
- def is_locked(self, item):
- return item in self.locks
-
- def lock(self, item):
-
- if item in self.locks:
- return False
- self.locks.add(item)
- return True
-
- def release(self, item):
-
- if not item in self.locks:
- raise ValueError("%s is not locked" % item)
- self.locks.remove(item)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/h5py.git
More information about the debian-science-commits
mailing list