Commit 9918b3f5 authored by Anssi Kääriäinen's avatar Anssi Kääriäinen
Browse files

[1.4.x] Fixed #19707 -- Reset transaction state after requests

Backpatch of a4e97cf3.
parent 498a5de0
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -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
+11 −0
Original line number Diff line number Diff line
@@ -83,6 +83,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
+15 −0
Original line number Diff line number Diff line
@@ -25,6 +25,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
+3 −0
Original line number Diff line number Diff line
@@ -97,6 +97,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)

+20 −1
Original line number Diff line number Diff line
@@ -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():
                # 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
Loading