Ticket #2227: transaction.py.diff

File transaction.py.diff, 6.9 KB (added by weaver@…, 18 years ago)
  • django/db/transaction.py

     
    1919try:
    2020    from functools import wraps
    2121except ImportError:
    22     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
     22    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
     23import threading
    2324from django.db import connection
    2425from django.conf import settings
    2526
    … …  
    3031    """
    3132    pass
    3233
    33 # The state is a dictionary of lists. The key to the dict is the current
    34 # thread and the list is handled as a stack of values.
    35 state = {}
     34class TransactionState(threading.local):
     35    __slots__ = ('managed', 'dirty')
     36   
     37    def __init__(self):
     38        self.managed = []
     39        self.dirty = False
    3640
    37 # The dirty flag is set by *_unless_managed functions to denote that the
    38 # code under transaction management has changed things to require a
    39 # database commit.
    40 dirty = {}
     41    def no_context(self):
     42        return not self.managed
    4143
     44    def top_level(self):
     45        return len(self.managed) == 1
     46
     47    def assert_managed(self):
     48        if self.no_context():
     49            raise TransactionManagementError("This code isn't under transaction management")
     50
     51    def really_transact(self):
     52        return self.top_level() or self.no_context()
     53
     54def assert_managed(proc):
     55
     56    def internal(*args, **kwargs):
     57        state.assert_managed()
     58        return proc(*args, **kwargs)
     59
     60    return wraps(proc)(internal)
     61
     62state = TransactionState()
     63
    4264def enter_transaction_management():
    4365    """
    4466    Enters transaction management for a running thread. It must be balanced with
    … …  
    4971    from the settings, if there is no surrounding block (dirty is always false
    5072    when no current block is running).
    5173    """
    52     thread_ident = thread.get_ident()
    53     if thread_ident in state and state[thread_ident]:
    54         state[thread_ident].append(state[thread_ident][-1])
    55     else:
    56         state[thread_ident] = []
    57         state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
    58     if thread_ident not in dirty:
    59         dirty[thread_ident] = False
     74    if not is_managed():
     75        commit()
     76    state.managed.append((False, is_managed()))
    6077
     78@assert_managed
    6179def leave_transaction_management():
    6280    """
    6381    Leaves transaction management for a running thread. A dirty flag is carried
    6482    over to the surrounding block, as a commit will commit all changes, even
    6583    those from outside. (Commits are on connection level.)
    6684    """
    67     thread_ident = thread.get_ident()
    68     if thread_ident in state and state[thread_ident]:
    69         del state[thread_ident][-1]
    70     else:
    71         raise TransactionManagementError("This code isn't under transaction management")
    72     if dirty.get(thread_ident, False):
     85    state.managed.pop()
     86    if state.dirty and state.no_context():
    7387        rollback()
    7488        raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
    75     dirty[thread_ident] = False
    7689
    7790def is_dirty():
    7891    """
    7992    Returns True if the current transaction requires a commit for changes to
    8093    happen.
    8194    """
    82     return dirty.get(thread.get_ident(), False)
     95    return state.dirty
    8396
    8497def set_dirty():
    8598    """
    … …  
    87100    to decide in a managed block of code to decide whether there are open
    88101    changes waiting for commit.
    89102    """
    90     thread_ident = thread.get_ident()
    91     if thread_ident in dirty:
    92         dirty[thread_ident] = True
    93     else:
    94         raise TransactionManagementError("This code isn't under transaction management")
     103    state.dirty = True
    95104
    96105def set_clean():
    97106    """
    … …  
    99108    to decide in a managed block of code to decide whether a commit or rollback
    100109    should happen.
    101110    """
    102     thread_ident = thread.get_ident()
    103     if thread_ident in dirty:
    104         dirty[thread_ident] = False
    105     else:
    106         raise TransactionManagementError("This code isn't under transaction management")
     111    state.dirty = False
    107112
    108113def is_managed():
    109     """
    110     Checks whether the transaction manager is in manual or in auto state.
    111     """
    112     thread_ident = thread.get_ident()
    113     if thread_ident in state:
    114         if state[thread_ident]:
    115             return state[thread_ident][-1]
    116     return settings.TRANSACTIONS_MANAGED
     114     """
     115     Checks whether the transaction manager is in manual or in auto state.
     116     """
     117     try:
     118         (initialized, managed) = state.managed[-1]
     119         return managed
     120     except IndexError:
     121        return settings.TRANSACTIONS_MANAGED
    117122
     123@assert_managed
    118124def managed(flag=True):
    119125    """
    120126    Puts the transaction manager into a manual state: managed transactions have
    121     to be committed explicitly by the user. If you switch off transaction
    122     management and there is a pending commit/rollback, the data will be
    123     commited.
     127    to be committed explicitly by the user.
    124128    """
    125     thread_ident = thread.get_ident()
    126     top = state.get(thread_ident, None)
    127     if top:
    128         top[-1] = flag
    129         if not flag and is_dirty():
    130             connection._commit()
    131             set_clean()
     129    (initialized, managed) = state.managed[-1]
     130    if not initialized:
     131        # Once a transaction is manually managed, subsequent nested
     132        # transactions cannot be automatically managed.
     133        state.managed[-1] = (True, managed or flag)
    132134    else:
    133         raise TransactionManagementError("This code isn't under transaction management")
     135        raise TransactionManagementError("`managed' cannot be called twice within the same transaction management block")
    134136
    135137def commit_unless_managed():
    136138    """
    137139    Commits changes if the system is not in managed transaction mode.
    138140    """
    139141    if not is_managed():
    140         connection._commit()
     142        commit()
    141143    else:
    142144        set_dirty()
    143145
    … …  
    146148    Rolls back changes if the system is not in managed transaction mode.
    147149    """
    148150    if not is_managed():
    149         connection._rollback()
     151        rollback()
    150152    else:
    151153        set_dirty()
    152154
    153155def commit():
    154156    """
    155     Does the commit itself and resets the dirty flag.
     157    Does the commit itself and resets the dirty flag.  This is a no-op
     158    unless the transaction is top-level.
    156159    """
    157     connection._commit()
    158     set_clean()
     160    if state.really_transact():
     161        connection._commit()
     162        set_clean()
    159163
    160164def rollback():
    161165    """
    162166    This function does the rollback itself and resets the dirty flag.
     167    This can only be called at the top-level.
    163168    """
    164     connection._rollback()
    165     set_clean()
     169    if state.really_transact():
     170        connection._rollback()
     171        set_clean()
     172    else:
     173        raise TransactionManagementError("`rollback' cannot reliably be called in a nested transaction.")   
    166174
    167175##############
    168176# DECORATORS #
    … …  
    196204            managed(True)
    197205            try:
    198206                res = func(*args, **kw)
    199             except Exception, e:
     207            except:
    200208                if is_dirty():
    201209                    rollback()
    202210                raise
Back to Top