diff --git a/django/db/__init__.py b/django/db/__init__.py
index b198048..548c2d5 100644
a
|
b
|
backend = load_backend(connection.settings_dict['ENGINE'])
|
42 | 42 | # Register an event that closes the database connection |
43 | 43 | # when a Django request is finished. |
44 | 44 | def close_connection(**kwargs): |
| 45 | # Avoid circular imports |
| 46 | from django.db import transaction |
45 | 47 | for conn in connections.all(): |
46 | 48 | conn.close() |
| 49 | # Make sure transaction_state isn't carried from one request to |
| 50 | # another. The weird structure here is caused by TestCase turning |
| 51 | # leave_transaction_management() to no-op. |
| 52 | tx_state_len = len(conn.transaction_state) |
| 53 | while tx_state_len: |
| 54 | try: |
| 55 | transaction.leave_transaction_management(using=conn.alias) |
| 56 | except transaction.TransactionManagementError: |
| 57 | # Even if we closed the connection above Django still thinks |
| 58 | # might be a transaction going on... |
| 59 | pass |
| 60 | if len(conn.transaction_state) == tx_state_len: |
| 61 | # It seems it was a no-op. |
| 62 | break |
| 63 | tx_state_len = len(conn.transaction_state) |
47 | 64 | signals.request_finished.connect(close_connection) |
48 | 65 | |
49 | 66 | # Register an event that resets connection.queries |
diff --git a/django/middleware/transaction.py b/django/middleware/transaction.py
index 96b1538..7888839 100644
a
|
b
|
class TransactionMiddleware(object):
|
15 | 15 | def process_exception(self, request, exception): |
16 | 16 | """Rolls back the database and leaves transaction management""" |
17 | 17 | if transaction.is_dirty(): |
| 18 | # This rollback might fail for closed connections for example. |
18 | 19 | transaction.rollback() |
19 | 20 | transaction.leave_transaction_management() |
20 | 21 | |
… |
… |
class TransactionMiddleware(object):
|
22 | 23 | """Commits and leaves transaction management.""" |
23 | 24 | if transaction.is_managed(): |
24 | 25 | if transaction.is_dirty(): |
25 | | transaction.commit() |
| 26 | # Note: it is possible that the commit fails. If the reason is |
| 27 | # closed connection or some similar reason, then there is |
| 28 | # little hope to proceed nicely. However, in case of deferred |
| 29 | # foreign key check failures we can still rollback(). In any |
| 30 | # case we want to have an informative exception raised. |
| 31 | try: |
| 32 | transaction.commit() |
| 33 | except: |
| 34 | # If the rollback fails, the transaction state will be |
| 35 | # messed up. It doesn't matter, the connection will be set |
| 36 | # to clean state after the request finishes. And, we can't |
| 37 | # clean the state here properly even if we wanted to, the |
| 38 | # connection is in transaction but we can't rollback... |
| 39 | transaction.rollback() |
| 40 | transaction.leave_transaction_management() |
| 41 | raise |
26 | 42 | transaction.leave_transaction_management() |
27 | 43 | return response |
diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py
index a9a45c9..6c43641 100644
a
|
b
|
import warnings
|
9 | 9 | |
10 | 10 | from django.conf import settings |
11 | 11 | from django.core import mail |
12 | | from django.db import transaction |
13 | | from django.http import HttpRequest |
14 | | from django.http import HttpResponse, StreamingHttpResponse |
| 12 | from django.db import (transaction, connections, DEFAULT_DB_ALIAS, |
| 13 | IntegrityError) |
| 14 | from django.http import HttpRequest, HttpResponse, StreamingHttpResponse |
15 | 15 | from django.middleware.clickjacking import XFrameOptionsMiddleware |
16 | 16 | from django.middleware.common import CommonMiddleware, BrokenLinkEmailsMiddleware |
17 | 17 | from django.middleware.http import ConditionalGetMiddleware |
… |
… |
class TransactionMiddlewareTest(TransactionTestCase):
|
710 | 710 | TransactionMiddleware().process_exception(self.request, None) |
711 | 711 | self.assertEqual(Band.objects.count(), 0) |
712 | 712 | self.assertFalse(transaction.is_dirty()) |
| 713 | |
| 714 | def test_failing_commit(self): |
| 715 | # It is possible that connection.commit() fails. Check that |
| 716 | # TransactionMiddleware handles such cases correctly. |
| 717 | try: |
| 718 | def raise_exception(): |
| 719 | raise IntegrityError() |
| 720 | connections[DEFAULT_DB_ALIAS].commit = raise_exception |
| 721 | transaction.enter_transaction_management() |
| 722 | transaction.managed(True) |
| 723 | Band.objects.create(name='The Beatles') |
| 724 | self.assertTrue(transaction.is_dirty()) |
| 725 | with self.assertRaises(IntegrityError): |
| 726 | TransactionMiddleware().process_response(self.request, None) |
| 727 | self.assertEqual(Band.objects.count(), 0) |
| 728 | self.assertFalse(transaction.is_dirty()) |
| 729 | self.assertFalse(transaction.is_managed()) |
| 730 | finally: |
| 731 | del connections[DEFAULT_DB_ALIAS].commit |
diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
index 799cd9b..a29fb36 100644
a
|
b
|
import warnings
|
6 | 6 | from datetime import datetime, timedelta |
7 | 7 | from io import BytesIO |
8 | 8 | |
| 9 | from django.db import connection |
| 10 | from django.core import signals |
9 | 11 | from django.core.exceptions import SuspiciousOperation |
10 | 12 | from django.core.handlers.wsgi import WSGIRequest, LimitedStream |
11 | 13 | from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr, UnreadablePostError |
| 14 | from django.test import TransactionTestCase |
12 | 15 | from django.test.client import FakePayload |
13 | 16 | from django.test.utils import override_settings, str_prefix |
14 | 17 | from django.utils import six |
… |
… |
class RequestsTests(unittest.TestCase):
|
524 | 527 | |
525 | 528 | with self.assertRaises(UnreadablePostError): |
526 | 529 | request.body |
| 530 | |
| 531 | class TransactionRequestTests(TransactionTestCase): |
| 532 | # Need to run the test under real transaction handling. |
| 533 | def test_request_finished_db_state(self): |
| 534 | # The GET below will not succeed, but it will give a response with |
| 535 | # defined ._handler_class. That is needed for sending the request_finished |
| 536 | # signal. However, the test client itself will not send request_finished |
| 537 | # signals. |
| 538 | response = self.client.get('/') |
| 539 | # Taking a cursor will open a new connection. |
| 540 | connection.cursor() |
| 541 | connection.enter_transaction_management() |
| 542 | signals.request_finished.send(sender=response._handler_class) |
| 543 | # in-memory sqlite doesn't actually close connections when connections are closed. |
| 544 | if connection.vendor != 'sqlite': |
| 545 | self.assertIs(connection.connection, None) |
| 546 | # And the transaction state is reset. |
| 547 | self.assertEqual(len(connection.transaction_state), 0) |