Commit 2ee21d9f authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Implemented persistent database connections.

Thanks Anssi Kääriäinen and Karen Tracey for their inputs.
parent d009ffe4
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ def check_password(environ, username, password):
            return None
        return user.check_password(password)
    finally:
        db.close_connection()
        db.close_old_connections()

def groups_for_user(environ, username):
    """
@@ -44,4 +44,4 @@ def groups_for_user(environ, username):
            return []
        return [force_bytes(group.name) for group in user.groups.all()]
    finally:
        db.close_connection()
        db.close_old_connections()
+16 −5
Original line number Diff line number Diff line
@@ -42,9 +42,10 @@ class DefaultConnectionProxy(object):
connection = DefaultConnectionProxy()
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):
    warnings.warn(
        "close_connection is superseded by close_old_connections.",
        PendingDeprecationWarning, stacklevel=2)
    # Avoid circular imports
    from django.db import transaction
    for conn in connections:
@@ -53,15 +54,25 @@ def close_connection(**kwargs):
        # connection state will be cleaned up.
        transaction.abort(conn)
        connections[conn].close()
signals.request_finished.connect(close_connection)

# Register an event that resets connection.queries
# when a Django request is started.
# Register an event to reset saved queries when a Django request is started.
def reset_queries(**kwargs):
    for conn in connections.all():
        conn.queries = []
signals.request_started.connect(reset_queries)

# Register an event to reset transaction state and close connections past
# their lifetime. NB: abort() doesn't do anything outside of a transaction.
def close_old_connections(**kwargs):
    for conn in connections.all():
        try:
            conn.abort()
        except DatabaseError:
            pass
        conn.close_if_unusable_or_obsolete()
signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)

# Register an event that rolls back the connections
# when a Django request has an exception.
def _rollback_on_exception(**kwargs):
+31 −1
Original line number Diff line number Diff line
import datetime
import time

from django.db.utils import DatabaseError

@@ -49,6 +50,10 @@ class BaseDatabaseWrapper(object):
        self._thread_ident = thread.get_ident()
        self.allow_thread_sharing = allow_thread_sharing

        # Connection termination related attributes
        self.close_at = None
        self.errors_occurred = False

    def __eq__(self, other):
        return self.alias == other.alias

@@ -59,7 +64,7 @@ class BaseDatabaseWrapper(object):
        return hash(self.alias)

    def wrap_database_errors(self):
        return DatabaseErrorWrapper(self.Database)
        return DatabaseErrorWrapper(self)

    def get_connection_params(self):
        raise NotImplementedError
@@ -76,6 +81,11 @@ class BaseDatabaseWrapper(object):
    def _cursor(self):
        with self.wrap_database_errors():
            if self.connection is None:
                # Reset parameters defining when to close the connection
                max_age = self.settings_dict['CONN_MAX_AGE']
                self.close_at = None if max_age is None else time.time() + max_age
                self.errors_occurred = False
                # Establish the connection
                conn_params = self.get_connection_params()
                self.connection = self.get_new_connection(conn_params)
                self.init_connection_state()
@@ -351,6 +361,26 @@ class BaseDatabaseWrapper(object):
            self.connection = None
        self.set_clean()

    def close_if_unusable_or_obsolete(self):
        if self.connection is not None:
            if self.errors_occurred:
                if self.is_usable():
                    self.errors_occurred = False
                else:
                    self.close()
                    return
            if self.close_at is not None and time.time() >= self.close_at:
                self.close()
                return

    def is_usable(self):
        """
        Test if the database connection is usable.

        This function may assume that self.connection is not None.
        """
        raise NotImplementedError

    def cursor(self):
        self.validate_thread_sharing()
        if (self.use_debug_cursor or
+8 −0
Original line number Diff line number Diff line
@@ -439,6 +439,14 @@ class DatabaseWrapper(BaseDatabaseWrapper):
        cursor = self.connection.cursor()
        return CursorWrapper(cursor)

    def is_usable(self):
        try:
            self.connection.ping()
        except DatabaseError:
            return False
        else:
            return True

    def _rollback(self):
        try:
            BaseDatabaseWrapper._rollback(self)
+12 −0
Original line number Diff line number Diff line
@@ -598,6 +598,18 @@ class DatabaseWrapper(BaseDatabaseWrapper):
            # stmtcachesize is available only in 4.3.2 and up.
            pass

    def is_usable(self):
        try:
            if hasattr(self.connection, 'ping'):    # Oracle 10g R2 and higher
                self.connection.ping()
            else:
                # Use a cx_Oracle cursor directly, bypassing Django's utilities.
                self.connection.cursor().execute("SELECT 1 FROM DUAL")
        except DatabaseError:
            return False
        else:
            return True

    # Oracle doesn't support savepoint commits.  Ignore them.
    def _savepoint_commit(self, sid):
        pass
Loading