]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Note time passage requirement for pool.invalidate()
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 27 Dec 2019 19:10:36 +0000 (14:10 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 31 Dec 2019 01:36:38 +0000 (20:36 -0500)
For Windows, time.time() may only have 16 millisecond
accuracy, so invalidation routines which compare
the time.time() of invalidate() to the time.time() when
the ConnectionRecord last connected may fail in a unit test
environment that does not pause at least this much time
since the ConnectionRecord startup.  Using >= for comparison
instead of > was considered but this only leads to more confusing
results as the ConnecitonRecord goes into a re-connect loop
as time continues to not pass.

Overall, while using routines such as Python 3.7's time_ns()
might be helpful, for now make sure tests which rely on this
are marked under timing intensive and add small sleeps.

Change-Id: I1a7162e67912d22c135fa517b687a073f8fd9151
(cherry picked from commit f1a22596e2283371f2216245ac4b7ff9a0fb6a9a)

lib/sqlalchemy/pool/base.py
test/engine/test_pool.py

index f98e0374332fe8aab763457c92149477df77bb93..222a968109ebfd1ed73bfa2c9a2cbb9469e2f94a 100644 (file)
@@ -588,6 +588,19 @@ class _ConnectionRecord(object):
 
     def get_connection(self):
         recycle = False
+
+        # NOTE: the various comparisons here are assuming that measurable time
+        # passes between these state changes.  however, time.time() is not
+        # guaranteed to have sub-second precision.  comparisons of
+        # "invalidation time" to "starttime" should perhaps use >= so that the
+        # state change can take place assuming no measurable  time has passed,
+        # however this does not guarantee correct behavior here as if time
+        # continues to not pass, it will try to reconnect repeatedly until
+        # these timestamps diverge, so in that sense using > is safer.  Per
+        # https://stackoverflow.com/a/1938096/34549, Windows time.time() may be
+        # within 16 milliseconds accuracy, so unit tests for connection
+        # invalidation need a sleep of at least this long between initial start
+        # time and invalidation for the logic below to work reliably.
         if self.connection is None:
             self.info.clear()
             self.__connect()
index 6cd2e457281e3647b07ddbd49f4caa9e759d4d3f..a52e7220fdd7a5278e0de3a0434e0f9009a39380 100644 (file)
@@ -1218,11 +1218,16 @@ class QueuePoolTest(PoolTestBase):
         is_(c2.connection, c_ref())
 
         c2_rec = c2._connection_record
+
+        # ensure pool invalidate time will be later than starttime
+        # for ConnectionRecord objects above
+        time.sleep(0.1)
         c2.invalidate(soft=True)
+
         is_(c2_rec.connection, c2.connection)
 
         c2.close()
-        time.sleep(0.5)
+
         c3 = p.connect()
         is_not_(c3.connection, c_ref())
         is_(c3._connection_record, c2_rec)
@@ -1286,6 +1291,7 @@ class QueuePoolTest(PoolTestBase):
         time.sleep(1.5)
         self._assert_cleanup_on_pooled_reconnect(dbapi, p)
 
+    @testing.requires.timing_intensive
     def test_connect_handler_not_called_for_recycled(self):
         """test [ticket:3497]"""
 
@@ -1301,6 +1307,10 @@ class QueuePoolTest(PoolTestBase):
 
         dbapi.shutdown(True)
 
+        # ensure pool invalidate time will be later than starttime
+        # for ConnectionRecord objects above
+        time.sleep(0.1)
+
         bad = p.connect()
         p._invalidate(bad)
         bad.close()
@@ -1324,6 +1334,7 @@ class QueuePoolTest(PoolTestBase):
             [call.connect(ANY, ANY), call.checkout(ANY, ANY, ANY)],
         )
 
+    @testing.requires.timing_intensive
     def test_connect_checkout_handler_always_gets_info(self):
         """test [ticket:3497]"""
 
@@ -1337,6 +1348,10 @@ class QueuePoolTest(PoolTestBase):
 
         dbapi.shutdown(True)
 
+        # ensure pool invalidate time will be later than starttime
+        # for ConnectionRecord objects above
+        time.sleep(0.1)
+
         bad = p.connect()
         p._invalidate(bad)
         bad.close()