summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAnssi Kääriäinen <akaariai@gmail.com>2013-02-05 23:52:29 +0200
committerAnssi Kääriäinen <akaariai@gmail.com>2013-02-10 13:55:54 +0200
commita4e97cf315142e61bb4bc3ed8259b95d8586d09c (patch)
tree7f21b7043e9a05a2dae6d8c833f98cb68f1c7fc9 /django
parent0e18fb04bad99de237b5eb8ea4f9ff2f3cd147d3 (diff)
Fixed #19707 -- Reset transaction state after requests
Diffstat (limited to 'django')
-rw-r--r--django/db/__init__.py13
-rw-r--r--django/db/backends/__init__.py11
-rw-r--r--django/db/transaction.py15
-rw-r--r--django/db/utils.py3
-rw-r--r--django/middleware/transaction.py21
-rw-r--r--django/test/testcases.py3
6 files changed, 63 insertions, 3 deletions
diff --git a/django/db/__init__.py b/django/db/__init__.py
index b1980488df..94eca13d41 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -42,8 +42,17 @@ backend = load_backend(connection.settings_dict['ENGINE'])
# Register an event that closes the database connection
# when a Django request is finished.
def close_connection(**kwargs):
- for conn in connections.all():
- conn.close()
+ # Avoid circular imports
+ from django.db import transaction
+ for conn in connections:
+ try:
+ transaction.abort(conn)
+ connections[conn].close()
+ except Exception:
+ # The connection's state is unknown, so it has to be
+ # abandoned. This could happen for example if the network
+ # connection has a failure.
+ del connections[conn]
signals.request_finished.connect(close_connection)
# Register an event that resets connection.queries
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 7dc5456827..bbb5a5b294 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -88,6 +88,17 @@ class BaseDatabaseWrapper(object):
return
self.cursor().execute(self.ops.savepoint_commit_sql(sid))
+ def abort(self):
+ """
+ Roll back any ongoing transaction and clean the transaction state
+ stack.
+ """
+ if self._dirty:
+ self._rollback()
+ self._dirty = False
+ while self.transaction_state:
+ self.leave_transaction_management()
+
def enter_transaction_management(self, managed=True):
"""
Enters transaction management for a running thread. It must be balanced with
diff --git a/django/db/transaction.py b/django/db/transaction.py
index f3ce2b2335..dd7e2f4dcb 100644
--- a/django/db/transaction.py
+++ b/django/db/transaction.py
@@ -24,6 +24,21 @@ class TransactionManagementError(Exception):
"""
pass
+def abort(using=None):
+ """
+ Roll back any ongoing transactions and clean the transaction management
+ state of the connection.
+
+ This method is to be used only in cases where using balanced
+ leave_transaction_management() calls isn't possible. For example after a
+ request has finished, the transaction state isn't known, yet the connection
+ must be cleaned up for the next request.
+ """
+ if using is None:
+ using = DEFAULT_DB_ALIAS
+ connection = connections[using]
+ connection.abort()
+
def enter_transaction_management(managed=True, using=None):
"""
Enters transaction management for a running thread. It must be balanced with
diff --git a/django/db/utils.py b/django/db/utils.py
index 91fa774ed4..943e3e3f73 100644
--- a/django/db/utils.py
+++ b/django/db/utils.py
@@ -99,6 +99,9 @@ class ConnectionHandler(object):
def __setitem__(self, key, value):
setattr(self._connections, key, value)
+ def __delitem__(self, key):
+ delattr(self._connections, key)
+
def __iter__(self):
return iter(self.databases)
diff --git a/django/middleware/transaction.py b/django/middleware/transaction.py
index 96b1538d9d..4440f377a7 100644
--- a/django/middleware/transaction.py
+++ b/django/middleware/transaction.py
@@ -15,6 +15,10 @@ class TransactionMiddleware(object):
def process_exception(self, request, exception):
"""Rolls back the database and leaves transaction management"""
if transaction.is_dirty():
+ # This rollback might fail because of network failure for example.
+ # If rollback isn't possible it is impossible to clean the
+ # connection's state. So leave the connection in dirty state and
+ # let request_finished signal deal with cleaning the connection.
transaction.rollback()
transaction.leave_transaction_management()
@@ -22,6 +26,21 @@ class TransactionMiddleware(object):
"""Commits and leaves transaction management."""
if transaction.is_managed():
if transaction.is_dirty():
- transaction.commit()
+ # Note: it is possible that the commit fails. If the reason is
+ # closed connection or some similar reason, then there is
+ # little hope to proceed nicely. However, in some cases (
+ # deferred foreign key checks for exampl) it is still possible
+ # to rollback().
+ try:
+ transaction.commit()
+ except Exception:
+ # If the rollback fails, the transaction state will be
+ # messed up. It doesn't matter, the connection will be set
+ # to clean state after the request finishes. And, we can't
+ # clean the state here properly even if we wanted to, the
+ # connection is in transaction but we can't rollback...
+ transaction.rollback()
+ transaction.leave_transaction_management()
+ raise
transaction.leave_transaction_management()
return response
diff --git a/django/test/testcases.py b/django/test/testcases.py
index 3aa0afa35e..f7c34a9f25 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -70,6 +70,7 @@ real_rollback = transaction.rollback
real_enter_transaction_management = transaction.enter_transaction_management
real_leave_transaction_management = transaction.leave_transaction_management
real_managed = transaction.managed
+real_abort = transaction.abort
def nop(*args, **kwargs):
return
@@ -80,6 +81,7 @@ def disable_transaction_methods():
transaction.enter_transaction_management = nop
transaction.leave_transaction_management = nop
transaction.managed = nop
+ transaction.abort = nop
def restore_transaction_methods():
transaction.commit = real_commit
@@ -87,6 +89,7 @@ def restore_transaction_methods():
transaction.enter_transaction_management = real_enter_transaction_management
transaction.leave_transaction_management = real_leave_transaction_management
transaction.managed = real_managed
+ transaction.abort = real_abort
def assert_and_parse_html(self, html, user_msg, msg):