Ticket #19707: ticket_19707.diff

File ticket_19707.diff, 7.0 KB (added by Anssi Kääriäinen, 4 years ago)
  • django/db/__init__.py

    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']) 
    4242# Register an event that closes the database connection
    4343# when a Django request is finished.
    4444def close_connection(**kwargs):
     45    # Avoid circular imports
     46    from django.db import transaction
    4547    for conn in connections.all():
    4648        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)
    4764signals.request_finished.connect(close_connection)
    4865
    4966# Register an event that resets connection.queries
  • django/middleware/transaction.py

    diff --git a/django/middleware/transaction.py b/django/middleware/transaction.py
    index 96b1538..7888839 100644
    a b class TransactionMiddleware(object): 
    1515    def process_exception(self, request, exception):
    1616        """Rolls back the database and leaves transaction management"""
    1717        if transaction.is_dirty():
     18            # This rollback might fail for closed connections for example.
    1819            transaction.rollback()
    1920        transaction.leave_transaction_management()
    2021
    class TransactionMiddleware(object): 
    2223        """Commits and leaves transaction management."""
    2324        if transaction.is_managed():
    2425            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
    2642            transaction.leave_transaction_management()
    2743        return response
  • tests/regressiontests/middleware/tests.py

    diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py
    index a9a45c9..6c43641 100644
    a b import warnings 
    99
    1010from django.conf import settings
    1111from django.core import mail
    12 from django.db import transaction
    13 from django.http import HttpRequest
    14 from django.http import HttpResponse, StreamingHttpResponse
     12from django.db import (transaction, connections, DEFAULT_DB_ALIAS,
     13                       IntegrityError)
     14from django.http import HttpRequest, HttpResponse, StreamingHttpResponse
    1515from django.middleware.clickjacking import XFrameOptionsMiddleware
    1616from django.middleware.common import CommonMiddleware, BrokenLinkEmailsMiddleware
    1717from django.middleware.http import ConditionalGetMiddleware
    class TransactionMiddlewareTest(TransactionTestCase): 
    710710        TransactionMiddleware().process_exception(self.request, None)
    711711        self.assertEqual(Band.objects.count(), 0)
    712712        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
  • tests/regressiontests/requests/tests.py

    diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
    index 799cd9b..a29fb36 100644
    a b import warnings 
    66from datetime import datetime, timedelta
    77from io import BytesIO
    88
     9from django.db import connection
     10from django.core import signals
    911from django.core.exceptions import SuspiciousOperation
    1012from django.core.handlers.wsgi import WSGIRequest, LimitedStream
    1113from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_repr, UnreadablePostError
     14from django.test import TransactionTestCase
    1215from django.test.client import FakePayload
    1316from django.test.utils import override_settings, str_prefix
    1417from django.utils import six
    class RequestsTests(unittest.TestCase): 
    524527
    525528        with self.assertRaises(UnreadablePostError):
    526529            request.body
     530
     531class 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)
Back to Top