]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Invalidate on failed connect handler
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 28 Mar 2018 15:58:47 +0000 (11:58 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 28 Mar 2018 19:49:17 +0000 (15:49 -0400)
Fixed bug in connection pool where a connection could be present in the
pool without all of its "connect" event handlers called, if a previous
"connect" handler threw an exception; note that the dialects themselves
have connect handlers that emit SQL, such as those which set transaction
isolation, which can fail if the database is in a non-available state, but
still allows a connection.  The connection is now invalidated first if any
of the connect handlers fail.

Change-Id: I61d6f4827a98ab8455f1c3e1c55d046eeccec09a
Fixes: #4225
doc/build/changelog/unreleased_12/4225.rst [new file with mode: 0644]
lib/sqlalchemy/pool.py
test/engine/test_pool.py

diff --git a/doc/build/changelog/unreleased_12/4225.rst b/doc/build/changelog/unreleased_12/4225.rst
new file mode 100644 (file)
index 0000000..99b0a6a
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: bug, engine
+    :tickets: 4225
+    :versions: 1.3.0b1
+
+    Fixed bug in connection pool where a connection could be present in the
+    pool without all of its "connect" event handlers called, if a previous
+    "connect" handler threw an exception; note that the dialects themselves
+    have connect handlers that emit SQL, such as those which set transaction
+    isolation, which can fail if the database is in a non-available state, but
+    still allows a connection.  The connection is now invalidated first if any
+    of the connect handlers fail.
index 102d50c18ea5dfb76e11c2a1a68356a156d5b601..89a4cea7c303b193975349f973ab66e02c1e74b5 100644 (file)
@@ -532,9 +532,9 @@ class _ConnectionRecord(object):
         rec = pool._do_get()
         try:
             dbapi_connection = rec.get_connection()
-        except:
+        except Exception as err:
             with util.safe_reraise():
-                rec.checkin()
+                rec._checkin_failed(err)
         echo = pool._should_log_debug()
         fairy = _ConnectionFairy(dbapi_connection, rec, echo)
         rec.fairy_ref = weakref.ref(
@@ -550,6 +550,10 @@ class _ConnectionRecord(object):
                               dbapi_connection)
         return fairy
 
+    def _checkin_failed(self, err):
+        self.invalidate(e=err)
+        self.checkin()
+
     def checkin(self):
         self.fairy_ref = None
         connection = self.connection
@@ -840,9 +844,9 @@ class _ConnectionFairy(object):
                 try:
                     fairy.connection = \
                         fairy._connection_record.get_connection()
-                except:
+                except Exception as err:
                     with util.safe_reraise():
-                        fairy._connection_record.checkin()
+                        fairy._connection_record._checkin_failed(err)
 
                 attempts -= 1
 
index e3f34dfa797ba054e2e5f5c77e5135aae5b926dd..b699afbc62c9879293b271d44d999e90872900fb 100644 (file)
@@ -730,6 +730,35 @@ class PoolEventsTest(PoolTestBase):
         p2.connect()
         eq_(canary, ["listen_one", "listen_two", "listen_one", "listen_three"])
 
+    def test_connect_event_fails_invalidates(self):
+        fail = False
+
+        def listen_one(conn, rec):
+            if fail:
+                raise Exception("it failed")
+
+        def listen_two(conn, rec):
+            rec.info['important_flag'] = True
+
+        p1 = pool.QueuePool(
+            creator=MockDBAPI().connect, pool_size=1, max_overflow=0)
+        event.listen(p1, 'connect', listen_one)
+        event.listen(p1, 'connect', listen_two)
+
+        conn = p1.connect()
+        eq_(conn.info['important_flag'], True)
+        conn.invalidate()
+        conn.close()
+
+        fail = True
+        assert_raises(Exception, p1.connect)
+
+        fail = False
+
+        conn = p1.connect()
+        eq_(conn.info['important_flag'], True)
+        conn.close()
+
     def teardown(self):
         # TODO: need to get remove() functionality
         # going