]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Revert "Use monotonic time for pool age measurement"
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 7 Oct 2020 14:09:41 +0000 (10:09 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 7 Oct 2020 14:09:41 +0000 (10:09 -0400)
This reverts commit 0220b58917b5a979891b5765f6ac5095e0368489.

I completely misread https://www.python.org/dev/peps/pep-0418/#rationale
and the accuracy of monotonic() is *worse* on windows than time.time(),
which is bizarre.

Change-Id: I2d571e268a2051bea68736507773d3904403af9e

doc/build/changelog/unreleased_14/monotonic_time.rst [deleted file]
lib/sqlalchemy/pool/base.py
lib/sqlalchemy/testing/requirements.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/compat.py
test/engine/test_pool.py
test/engine/test_reconnect.py

diff --git a/doc/build/changelog/unreleased_14/monotonic_time.rst b/doc/build/changelog/unreleased_14/monotonic_time.rst
deleted file mode 100644 (file)
index dc733d8..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-.. change::
-    :tags: change, bug
-
-    The internal clock used by the :class:`_pool.Pool` object is now
-    time.monotonic_time() under Python 3.  Under Python 2, time.time() is still
-    used, which is legacy. This clock is used to measure the age of a
-    connection against its starttime, and used in comparisons against the
-    pool_timeout setting as well as the last time the pool was marked as
-    invalid to determine if the connection should be recycled. Previously,
-    time.time() was used which was subject to inaccuracies as a result of
-    system clock changes as well as poor time resolution on windows.
index 2f1959f7c8bd633667f5b7360533c99cc9d50274..87383fef717a6a1d76b44e4d625d6eb405a9f431 100644 (file)
 """
 
 from collections import deque
+import time
 import weakref
 
 from .. import event
 from .. import exc
 from .. import log
 from .. import util
-from ..util import monotonic_time
 from ..util import threading
 
+
 reset_rollback = util.symbol("reset_rollback")
 reset_commit = util.symbol("reset_commit")
 reset_none = util.symbol("reset_none")
@@ -260,7 +261,7 @@ class Pool(log.Identified):
         """
         rec = getattr(connection, "_connection_record", None)
         if not rec or self._invalidate_time < rec.starttime:
-            self._invalidate_time = monotonic_time()
+            self._invalidate_time = time.time()
         if _checkin and getattr(connection, "is_valid", False):
             connection.invalidate(exception)
 
@@ -509,7 +510,7 @@ class _ConnectionRecord(object):
                 self.connection,
             )
         if soft:
-            self._soft_invalidate_time = monotonic_time()
+            self._soft_invalidate_time = time.time()
         else:
             self.__close()
             self.connection = None
@@ -518,16 +519,23 @@ class _ConnectionRecord(object):
         recycle = False
 
         # NOTE: the various comparisons here are assuming that measurable time
-        # passes between these state changes.  on Python 2, we are still using
-        # time.time() which is not guaranteed to have millisecondsecond
-        # precision, i.e. on Windows. For Python 3 we now use monotonic_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()
         elif (
             self.__pool._recycle > -1
-            and monotonic_time() - self.starttime > self.__pool._recycle
+            and time.time() - self.starttime > self.__pool._recycle
         ):
             self.__pool.logger.info(
                 "Connection %r exceeded timeout; recycling", self.connection
@@ -569,7 +577,7 @@ class _ConnectionRecord(object):
         # creator fails, this attribute stays None
         self.connection = None
         try:
-            self.starttime = monotonic_time()
+            self.starttime = time.time()
             connection = pool._invoke_creator(self)
             pool.logger.debug("Created new connection %r", connection)
             self.connection = connection
index e2c61a05eadd8d31ab52b13b844af6c95377c5a4..45a2fdf3176ca19c9f7449448beb546091370d2a 100644 (file)
@@ -15,6 +15,7 @@ to provide specific inclusion/exclusions.
 
 """
 
+import platform
 import sys
 
 from . import exclusions
@@ -1092,21 +1093,10 @@ class SuiteRequirements(Requirements):
 
     def _running_on_windows(self):
         return exclusions.LambdaPredicate(
-            lambda: util.win32,
+            lambda: platform.system() == "Windows",
             description="running on Windows",
         )
 
-    @property
-    def millisecond_monotonic_time(self):
-        """the util.monotonic_time() function must be millisecond accurate.
-
-        Under Python 2 we can't guarantee this on Windows.
-
-        """
-        return exclusions.skip_if(
-            lambda: util.win32 and sys.version_info < (3,)
-        )
-
     @property
     def timing_intensive(self):
         return exclusions.requires_tag("timing_intensive")
index c0b328d5e9e824be9dc84d596bd29f7b47eda5d2..8ef2f010321879f0cce341cc04c63a6f2171f096 100644 (file)
@@ -64,7 +64,6 @@ from .compat import int_types  # noqa
 from .compat import iterbytes  # noqa
 from .compat import itertools_filter  # noqa
 from .compat import itertools_filterfalse  # noqa
-from .compat import monotonic_time  # noqa
 from .compat import namedtuple  # noqa
 from .compat import next  # noqa
 from .compat import osx  # noqa
index b94ea353e20e522c4ece250f7d523113bc99309c..d3fefa526578ddc19315c7f100f1fc62d86df245 100644 (file)
@@ -109,8 +109,6 @@ if py3k:
     from io import StringIO
     from itertools import zip_longest
     from time import perf_counter
-    from time import monotonic as monotonic_time
-
     from urllib.parse import (
         quote_plus,
         unquote_plus,
@@ -209,7 +207,6 @@ else:
     from cStringIO import StringIO as byte_buffer  # noqa
     from itertools import izip_longest as zip_longest  # noqa
     from time import clock as perf_counter  # noqa
-    from time import time as monotonic_time  # noqa
     from urllib import quote  # noqa
     from urllib import quote_plus  # noqa
     from urllib import unquote  # noqa
index e01609b17548ac02a2dfc94592df12398d8c7fbc..eb705da61abe88ca3d1eab20506f52f4e9289b95 100644 (file)
@@ -1241,7 +1241,7 @@ class QueuePoolTest(PoolTestBase):
         )
 
     def test_recycle(self):
-        with patch("sqlalchemy.pool.base.monotonic_time") as mock:
+        with patch("sqlalchemy.pool.base.time.time") as mock:
             mock.return_value = 10000
 
             p = self._queuepool_fixture(
index 8881620f78480ce0cdf1042bf29bd5fd19d482fe..df95798fdfb0dfc0d3e9519fdfda27a2ed648e60 100644 (file)
@@ -357,8 +357,6 @@ class PrePingMockTest(fixtures.TestBase):
 
 
 class MockReconnectTest(fixtures.TestBase):
-    __requires__ = ("millisecond_monotonic_time",)
-
     def setup(self):
         self.dbapi = MockDBAPI()
 
@@ -427,14 +425,8 @@ class MockReconnectTest(fixtures.TestBase):
             [[call()], []],
         )
 
-        # checkout makes use of the same connection record.   in
-        # get_connection(), recycle should be enabled because
-        # the age of the connection is older than the time at which
-        # the invalidation occurred.
         conn = self.db.connect()
 
-        # therefore the two connections should both have been closed
-        # when we connected again.
         eq_(
             [c.close.mock_calls for c in self.dbapi.connections],
             [[call()], [call()], []],