]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Detect PyMySQL connection was killed
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 11 Nov 2019 21:17:50 +0000 (16:17 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 11 Nov 2019 21:20:42 +0000 (16:20 -0500)
Added "Connection was killed" message interpreted from the base
pymysql.Error class in order to detect closed connection, based on reports
that this message is arriving via a pymysql.InternalError() object which
indicates pymysql is not handling it correctly.

Change-Id: If6bbe0eb5993e1996c0c5de752eebaf7446cf93e
References: https://github.com/PyMySQL/PyMySQL/issues/816
Fixes: #4945
(cherry picked from commit 31a66b5add39b9193975f9995ce5cd9235179f00)

doc/build/changelog/unreleased_13/4945.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/pymysql.py
test/dialect/mysql/test_dialect.py

diff --git a/doc/build/changelog/unreleased_13/4945.rst b/doc/build/changelog/unreleased_13/4945.rst
new file mode 100644 (file)
index 0000000..f94efca
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: bug, mysql
+    :tickets: 4945
+
+    Added "Connection was killed" message interpreted from the base
+    pymysql.Error class in order to detect closed connection, based on reports
+    that this message is arriving via a pymysql.InternalError() object which
+    indicates pymysql is not handling it correctly.
index 0b812cbb035aa36ff5e4045e3edf364e542a56eb..62de1529f2312a8984530061c972de9290d5e69d 100644 (file)
@@ -67,7 +67,10 @@ class MySQLDialect_pymysql(MySQLDialect_mysqldb):
         ):
             return True
         elif isinstance(e, self.dbapi.Error):
-            return "Already closed" in str(e)
+            str_e = str(e).lower()
+            return (
+                "already closed" in str_e or "connection was killed" in str_e
+            )
         else:
             return False
 
index 26c7d38b8c9b2eb16d3dac8d875803b1da3d6fe1..aac1f6489235a8f98a9c5d0fda7e5afdb1200bed 100644 (file)
@@ -16,6 +16,7 @@ from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing import mock
 from ...engine import test_execute
 
 
@@ -23,6 +24,33 @@ class DialectTest(fixtures.TestBase):
     __backend__ = True
     __only_on__ = "mysql"
 
+    @testing.combinations(
+        (None, "cONnection was kILLEd", "InternalError", "pymysql", True),
+        (None, "cONnection aLREady closed", "InternalError", "pymysql", True),
+        (None, "something broke", "InternalError", "pymysql", False),
+        (2006, "foo", "OperationalError", "mysqldb", True),
+        (2006, "foo", "OperationalError", "pymysql", True),
+        (2007, "foo", "OperationalError", "mysqldb", False),
+        (2007, "foo", "OperationalError", "pymysql", False),
+    )
+    def test_is_disconnect(
+        self, arg0, message, exc_cls_name, dialect_name, is_disconnect
+    ):
+        class Error(Exception):
+            pass
+
+        dbapi = mock.Mock()
+        dbapi.Error = Error
+        dbapi.ProgrammingError = type("ProgrammingError", (Error,), {})
+        dbapi.OperationalError = type("OperationalError", (Error,), {})
+        dbapi.InterfaceError = type("InterfaceError", (Error,), {})
+        dbapi.InternalError = type("InternalError", (Error,), {})
+
+        dialect = getattr(mysql, dialect_name).dialect(dbapi=dbapi)
+
+        error = getattr(dbapi, exc_cls_name)(arg0, message)
+        eq_(dialect.is_disconnect(error, None, None), is_disconnect)
+
     def test_ssl_arguments_mysqldb(self):
         from sqlalchemy.dialects.mysql import mysqldb