Bladeren bron

Fixed #21553 -- Ensured unusable database connections get closed.

Aymeric Augustin 11 jaren geleden
bovenliggende
commit
5f2f47fdfc

+ 6 - 1
django/db/backends/__init__.py

@@ -351,9 +351,14 @@ class BaseDatabaseWrapper(object):
     def is_usable(self):
         """
         Tests if the database connection is usable.
+
         This function may assume that self.connection is not None.
+
+        Actual implementations should take care not to raise exceptions
+        as that may prevent Django from recycling unusable connections.
         """
-        raise NotImplementedError('subclasses of BaseDatabaseWrapper may require an is_usable() method')
+        raise NotImplementedError(
+            "subclasses of BaseDatabaseWrapper may require an is_usable() method")
 
     def close_if_unusable_or_obsolete(self):
         """

+ 1 - 1
django/db/backends/mysql/base.py

@@ -552,7 +552,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
     def is_usable(self):
         try:
             self.connection.ping()
-        except DatabaseError:
+        except Database.Error:
             return False
         else:
             return True

+ 1 - 1
django/db/backends/oracle/base.py

@@ -704,7 +704,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
             else:
                 # Use a cx_Oracle cursor directly, bypassing Django's utilities.
                 self.connection.cursor().execute("SELECT 1 FROM DUAL")
-        except DatabaseError:
+        except Database.Error:
             return False
         else:
             return True

+ 1 - 1
django/db/backends/postgresql_psycopg2/base.py

@@ -210,7 +210,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
         try:
             # Use a psycopg cursor directly, bypassing Django's utilities.
             self.connection.cursor().execute("SELECT 1")
-        except DatabaseError:
+        except Database.Error:
             return False
         else:
             return True

+ 31 - 0
tests/backends/tests.py

@@ -665,6 +665,37 @@ class BackendTestCase(TestCase):
         self.assertTrue(cursor.closed)
 
 
+class IsUsableTests(TransactionTestCase):
+    # Avoid using a regular TestCase because Django really dislikes closing
+    # the database connection inside a transaction at this point (#21202).
+
+    available_apps = []
+
+    # Unfortunately with sqlite3 the in-memory test database cannot be closed.
+    @skipUnlessDBFeature('test_db_allows_multiple_connections')
+    def test_is_usable_after_database_disconnects(self):
+        """
+        Test that is_usable() doesn't crash when the database disconnects.
+
+        Regression for #21553.
+        """
+        # Open a connection to the database.
+        with connection.cursor():
+            pass
+        # Emulate a connection close by the database.
+        connection._close()
+        # Even then is_usable() should not raise an exception.
+        try:
+            self.assertFalse(connection.is_usable())
+        finally:
+            # Clean up the mess created by connection._close(). Since the
+            # connection is already closed, this crashes on some backends.
+            try:
+                connection.close()
+            except Exception:
+                pass
+
+
 # We don't make these tests conditional because that means we would need to
 # check and differentiate between:
 # * MySQL+InnoDB, MySQL+MYISAM (something we currently can't do).