import sys, threading from . import custom from .benchmark import benchmarking from .cache import cache_hook # Transaction-kankei class TransactionAborted(Exception): pass state = threading.local() # Set by model sql_hook = None class withness(object): def __enter__(self): return self def __exit__(self,a,b,c): if a is None: commit_transaction() else: abort_transaction() # Current commit protocol: # begin MySQL # do stuff, beginning version_control_hook at the first mod # prepare MySQL (i.e., flush everything to make sure we have all the locks we need and won't deadlock) # commit the version_control_hook # if that succeeds: # commit MySQL (shouldn't fail because it doesn't require more locks) # # See also hook method descs in bazbase.custom. def begin_transaction(benchmark=True): from cStringIO import StringIO state.readonly = True state.log = StringIO() state.revision = None if benchmark: benchmarking.start(state.log) with benchmarking('beginning sql'): sql_hook.begin() with benchmarking('beginning cache'): cache_hook.begin() return withness() # Raises TransactionAborted if the commit fails def commit_transaction(): success = False try: with benchmarking('preparing sql'): sql_hook.prepare() except Exception,e: sql_hook.abort() if not state.readonly and custom.version_control_hook is not None: custom.version_control_hook.abort() raise #TransactionAborted(e) try: if not state.readonly and custom.version_control_hook is not None: with benchmarking('committing vcs'): state.revision = custom.version_control_hook.commit() except: cache_hook.abort() sql_hook.abort() raise else: with benchmarking('committing cache'): cache_hook.commit() with benchmarking('committing sql'): sql_hook.commit() def abort_transaction(): # It's ok to keep cached stuff if there weren't any changes made. try: if state.readonly: with benchmarking('abort committing cache'): cache_hook.commit() else: cache_hook.abort() finally: # We want to do this even if there's something up with the cache hook. sql_hook.abort() if not state.readonly and custom.version_control_hook is not None: custom.version_control_hook.abort() def maybe_update_auth_post_setprop(element, pname, val, format=None): from . import structure if pname in (u'username', u'password'): anc = structure.get_element(custom.EDITOR_ANCESTOR) # anc will be None in bazbase unit tests and if we're bootstrapping # (User being an ancestor of Admin). if anc is not None and anc.is_ancestor_of(element): custom.update_auth() def __passthrough(name): def hookhelper(*args,**kw): if state.readonly: state.readonly = False if custom.version_control_hook is not None: with benchmarking('beginning vcs'): state.revision = custom.version_control_hook.begin() getattr(sql_hook, name)(*args, **kw) if custom.version_control_hook is not None: getattr(custom.version_control_hook, name)(*args, **kw) cache_hook.clear() if hasattr(sql_hook,'post_'+name): getattr(sql_hook,'post_'+name)(*args,**kw) if name == 'setprop': maybe_update_auth_post_setprop(*args, **kw) return hookhelper # Hook passthrough functions setprop = __passthrough('setprop') delete = __passthrough('delete') esetattr = __passthrough('esetattr') edelete = __passthrough('edelete') psetattr = __passthrough('psetattr') pdelete = __passthrough('pdelete') def get_log(): if hasattr(state, 'log'): return state.log.getvalue() else: return "" def get_revision(): if state.revision is None and custom.version_control_hook is not None: state.revision = custom.version_control_hook.get_revision() return state.revision