Index: django/db/transaction.py
===================================================================
--- django/db/transaction.py	(revision 7679)
+++ django/db/transaction.py	(working copy)
@@ -19,7 +19,8 @@
 try:
     from functools import wraps
 except ImportError:
-    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback. 
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
+import threading
 from django.db import connection
 from django.conf import settings
 
@@ -30,15 +31,36 @@
     """
     pass
 
-# The state is a dictionary of lists. The key to the dict is the current
-# thread and the list is handled as a stack of values.
-state = {}
+class TransactionState(threading.local):
+    __slots__ = ('managed', 'dirty')
+    
+    def __init__(self):
+	self.managed = []
+	self.dirty = False
 
-# The dirty flag is set by *_unless_managed functions to denote that the
-# code under transaction management has changed things to require a
-# database commit.
-dirty = {}
+    def no_context(self):
+	return not self.managed
 
+    def top_level(self):
+	return len(self.managed) == 1
+
+    def assert_managed(self):
+	if self.no_context():
+	    raise TransactionManagementError("This code isn't under transaction management")
+
+    def really_transact(self):
+	return self.top_level() or self.no_context()
+
+def assert_managed(proc):
+
+    def internal(*args, **kwargs):
+	state.assert_managed()
+	return proc(*args, **kwargs)
+
+    return wraps(proc)(internal)
+
+state = TransactionState()
+
 def enter_transaction_management():
     """
     Enters transaction management for a running thread. It must be balanced with
@@ -49,37 +71,28 @@
     from the settings, if there is no surrounding block (dirty is always false
     when no current block is running).
     """
-    thread_ident = thread.get_ident()
-    if thread_ident in state and state[thread_ident]:
-        state[thread_ident].append(state[thread_ident][-1])
-    else:
-        state[thread_ident] = []
-        state[thread_ident].append(settings.TRANSACTIONS_MANAGED)
-    if thread_ident not in dirty:
-        dirty[thread_ident] = False
+    if not is_managed():
+	commit()
+    state.managed.append((False, is_managed()))
 
+@assert_managed
 def leave_transaction_management():
     """
     Leaves transaction management for a running thread. A dirty flag is carried
     over to the surrounding block, as a commit will commit all changes, even
     those from outside. (Commits are on connection level.)
     """
-    thread_ident = thread.get_ident()
-    if thread_ident in state and state[thread_ident]:
-        del state[thread_ident][-1]
-    else:
-        raise TransactionManagementError("This code isn't under transaction management")
-    if dirty.get(thread_ident, False):
+    state.managed.pop()
+    if state.dirty and state.no_context():
         rollback()
         raise TransactionManagementError("Transaction managed block ended with pending COMMIT/ROLLBACK")
-    dirty[thread_ident] = False
 
 def is_dirty():
     """
     Returns True if the current transaction requires a commit for changes to
     happen.
     """
-    return dirty.get(thread.get_ident(), False)
+    return state.dirty
 
 def set_dirty():
     """
@@ -87,11 +100,7 @@
     to decide in a managed block of code to decide whether there are open
     changes waiting for commit.
     """
-    thread_ident = thread.get_ident()
-    if thread_ident in dirty:
-        dirty[thread_ident] = True
-    else:
-        raise TransactionManagementError("This code isn't under transaction management")
+    state.dirty = True
 
 def set_clean():
     """
@@ -99,45 +108,38 @@
     to decide in a managed block of code to decide whether a commit or rollback
     should happen.
     """
-    thread_ident = thread.get_ident()
-    if thread_ident in dirty:
-        dirty[thread_ident] = False
-    else:
-        raise TransactionManagementError("This code isn't under transaction management")
+    state.dirty = False
 
 def is_managed():
-    """
-    Checks whether the transaction manager is in manual or in auto state.
-    """
-    thread_ident = thread.get_ident()
-    if thread_ident in state:
-        if state[thread_ident]:
-            return state[thread_ident][-1]
-    return settings.TRANSACTIONS_MANAGED
+     """
+     Checks whether the transaction manager is in manual or in auto state.
+     """
+     try:
+	 (initialized, managed) = state.managed[-1]
+	 return managed
+     except IndexError:
+	 return settings.TRANSACTIONS_MANAGED
 
+@assert_managed
 def managed(flag=True):
     """
     Puts the transaction manager into a manual state: managed transactions have
-    to be committed explicitly by the user. If you switch off transaction
-    management and there is a pending commit/rollback, the data will be
-    commited.
+    to be committed explicitly by the user.
     """
-    thread_ident = thread.get_ident()
-    top = state.get(thread_ident, None)
-    if top:
-        top[-1] = flag
-        if not flag and is_dirty():
-            connection._commit()
-            set_clean()
+    (initialized, managed) = state.managed[-1]
+    if not initialized:
+	# Once a transaction is manually managed, subsequent nested
+	# transactions cannot be automatically managed.
+	state.managed[-1] = (True, managed or flag)
     else:
-        raise TransactionManagementError("This code isn't under transaction management")
+	raise TransactionManagementError("`managed' cannot be called twice within the same transaction management block")
 
 def commit_unless_managed():
     """
     Commits changes if the system is not in managed transaction mode.
     """
     if not is_managed():
-        connection._commit()
+        commit()
     else:
         set_dirty()
 
@@ -146,23 +148,29 @@
     Rolls back changes if the system is not in managed transaction mode.
     """
     if not is_managed():
-        connection._rollback()
+        rollback()
     else:
         set_dirty()
 
 def commit():
     """
-    Does the commit itself and resets the dirty flag.
+    Does the commit itself and resets the dirty flag.  This is a no-op
+    unless the transaction is top-level.
     """
-    connection._commit()
-    set_clean()
+    if state.really_transact():
+	connection._commit()
+	set_clean()
 
 def rollback():
     """
     This function does the rollback itself and resets the dirty flag.
+    This can only be called at the top-level.
     """
-    connection._rollback()
-    set_clean()
+    if state.really_transact():
+	connection._rollback()
+	set_clean()
+    else:
+	raise TransactionManagementError("`rollback' cannot reliably be called in a nested transaction.")    
 
 ##############
 # DECORATORS #
@@ -196,7 +204,7 @@
             managed(True)
             try:
                 res = func(*args, **kw)
-            except Exception, e:
+            except:
                 if is_dirty():
                     rollback()
                 raise
