From cf877e95c7ed231f61ccac20e9ecbb4920de3741 Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Mon, 25 Jan 2021 12:46:54 -0800 Subject: [PATCH] PYTHON-2503 Always use time.monotonic For monotonic time needs. --- pymongo/client_session.py | 12 +++++----- pymongo/monitor.py | 14 ++++++------ pymongo/monotonic.py | 38 -------------------------------- pymongo/network.py | 8 +++---- pymongo/periodic_executor.py | 6 ++--- pymongo/pool.py | 14 ++++++------ pymongo/pyopenssl_context.py | 6 ++--- pymongo/server_description.py | 5 +++-- pymongo/topology.py | 18 +++++++-------- test/performance/perf_test.py | 10 ++++----- test/test_client.py | 5 ++--- test/test_monotonic.py | 41 ----------------------------------- test/test_session.py | 4 ++-- 13 files changed, 50 insertions(+), 131 deletions(-) delete mode 100644 pymongo/monotonic.py delete mode 100644 test/test_monotonic.py diff --git a/pymongo/client_session.py b/pymongo/client_session.py index 13a70840d..245ba5d45 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -98,6 +98,7 @@ Classes """ import collections +import time import uuid from collections.abc import Mapping as _Mapping @@ -107,7 +108,6 @@ from bson.int64 import Int64 from bson.son import SON from bson.timestamp import Timestamp -from pymongo import monotonic from pymongo.errors import (ConfigurationError, ConnectionFailure, InvalidOperation, @@ -336,7 +336,7 @@ _WITH_TRANSACTION_RETRY_TIME_LIMIT = 120 def _within_time_limit(start_time): """Are we within the with_transaction retry limit?""" - return monotonic.time() - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT + return time.monotonic() - start_time < _WITH_TRANSACTION_RETRY_TIME_LIMIT class ClientSession(object): @@ -518,7 +518,7 @@ class ClientSession(object): .. versionadded:: 3.9 """ - start_time = monotonic.time() + start_time = time.monotonic() while True: self.start_transaction( read_concern, write_concern, read_preference, @@ -797,7 +797,7 @@ class ClientSession(object): def _apply_to(self, command, is_retryable, read_preference): self._check_ended() - self._server_session.last_use = monotonic.time() + self._server_session.last_use = time.monotonic() command['lsid'] = self._server_session.session_id if not self.in_transaction: @@ -842,7 +842,7 @@ class _ServerSession(object): def __init__(self, generation): # Ensure id is type 4, regardless of CodecOptions.uuid_representation. self.session_id = {'id': Binary(uuid.uuid4().bytes, 4)} - self.last_use = monotonic.time() + self.last_use = time.monotonic() self._transaction_id = 0 self.dirty = False self.generation = generation @@ -856,7 +856,7 @@ class _ServerSession(object): self.dirty = True def timed_out(self, session_timeout_minutes): - idle_seconds = monotonic.time() - self.last_use + idle_seconds = time.monotonic() - self.last_use # Timed out if we have less than a minute to live. return idle_seconds > (session_timeout_minutes - 1) * 60 diff --git a/pymongo/monitor.py b/pymongo/monitor.py index 1579ec513..7a3a4f22b 100644 --- a/pymongo/monitor.py +++ b/pymongo/monitor.py @@ -16,6 +16,7 @@ import atexit import threading +import time import weakref from pymongo import common, periodic_executor @@ -23,7 +24,6 @@ from pymongo.errors import (NotMasterError, OperationFailure, _OperationCancelled) from pymongo.ismaster import IsMaster -from pymongo.monotonic import time as _time from pymongo.periodic_executor import _shutdown_executors from pymongo.read_preferences import MovingAverage from pymongo.server_description import ServerDescription @@ -208,7 +208,7 @@ class Monitor(MonitorBase): Returns a ServerDescription. """ - start = _time() + start = time.monotonic() try: try: return self._check_once() @@ -223,7 +223,7 @@ class Monitor(MonitorBase): _sanitize(error) sd = self._server_description address = sd.address - duration = _time() - start + duration = time.monotonic() - start if self._publish: awaited = sd.is_server_type_known and sd.topology_version self._listeners.publish_server_heartbeat_failed( @@ -265,7 +265,7 @@ class Monitor(MonitorBase): Can raise ConnectionFailure or OperationFailure. """ cluster_time = self._topology.max_cluster_time() - start = _time() + start = time.monotonic() if conn.more_to_come: # Read the next streaming isMaster (MongoDB 4.4+). response = IsMaster(conn._next_reply(), awaitable=True) @@ -280,7 +280,7 @@ class Monitor(MonitorBase): else: # New connection handshake or polling isMaster (MongoDB <4.4). response = conn._ismaster(cluster_time, None, None, None) - return response, _time() - start + return response, time.monotonic() - start class SrvMonitor(MonitorBase): @@ -388,9 +388,9 @@ class _RttMonitor(MonitorBase): with self._pool.get_socket({}) as sock_info: if self._executor._stopped: raise Exception('_RttMonitor closed') - start = _time() + start = time.monotonic() sock_info.ismaster() - return _time() - start + return time.monotonic() - start # Close monitors to cancel any in progress streaming checks before joining diff --git a/pymongo/monotonic.py b/pymongo/monotonic.py deleted file mode 100644 index 3be25b8b1..000000000 --- a/pymongo/monotonic.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2014-2015 MongoDB, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Time. Monotonic if possible. -""" - -from __future__ import absolute_import - -__all__ = ['time'] - -try: - # Patches standard time module. - # From https://pypi.python.org/pypi/Monotime. - import monotime -except ImportError: - pass - -try: - # From https://pypi.python.org/pypi/monotonic. - from monotonic import monotonic as time -except ImportError: - try: - # Monotime or Python 3. - from time import monotonic as time - except ImportError: - # Not monotonic. - from time import time diff --git a/pymongo/network.py b/pymongo/network.py index 5a5852f45..682a51df7 100644 --- a/pymongo/network.py +++ b/pymongo/network.py @@ -18,6 +18,7 @@ import datetime import errno import socket import struct +import time from bson import _decode_all_selective @@ -31,7 +32,6 @@ from pymongo.errors import (AutoReconnect, ProtocolError, _OperationCancelled) from pymongo.message import _UNPACK_REPLY, _OpMsg -from pymongo.monotonic import time from pymongo.socket_checker import _errno_from_exception @@ -185,7 +185,7 @@ def receive_message(sock_info, request_id, max_message_size=MAX_MESSAGE_SIZE): """Receive a raw BSON message or raise socket.error.""" timeout = sock_info.sock.gettimeout() if timeout: - deadline = time() + timeout + deadline = time.monotonic() + timeout else: deadline = None # Ignore the response's request id. @@ -236,7 +236,7 @@ def wait_for_read(sock_info, deadline): # Wait up to 500ms for the socket to become readable and then # check for cancellation. if deadline: - timeout = max(min(deadline - time(), _POLL_TIMEOUT), 0.001) + timeout = max(min(deadline - time.monotonic(), _POLL_TIMEOUT), 0.001) else: timeout = _POLL_TIMEOUT readable = sock_info.socket_checker.select( @@ -245,7 +245,7 @@ def wait_for_read(sock_info, deadline): raise _OperationCancelled('isMaster cancelled') if readable: return - if deadline and time() > deadline: + if deadline and time.monotonic() > deadline: raise socket.timeout("timed out") def _receive_data_on_socket(sock_info, length, deadline): diff --git a/pymongo/periodic_executor.py b/pymongo/periodic_executor.py index 09ff41120..e1690ee9b 100644 --- a/pymongo/periodic_executor.py +++ b/pymongo/periodic_executor.py @@ -18,8 +18,6 @@ import threading import time import weakref -from pymongo.monotonic import time as _time - class PeriodicExecutor(object): def __init__(self, interval, min_interval, target, name=None): @@ -135,8 +133,8 @@ class PeriodicExecutor(object): if self._skip_sleep: self._skip_sleep = False else: - deadline = _time() + self._interval - while not self._stopped and _time() < deadline: + deadline = time.monotonic() + self._interval + while not self._stopped and time.monotonic() < deadline: time.sleep(self._min_interval) if self._event: break # Early wake. diff --git a/pymongo/pool.py b/pymongo/pool.py index 6dae7a644..2b433875c 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -12,6 +12,7 @@ # implied. See the License for the specific language governing # permissions and limitations under the License. +import collections import contextlib import copy import ipaddress @@ -20,7 +21,7 @@ import platform import socket import sys import threading -import collections +import time from bson import DEFAULT_CODEC_OPTIONS from bson.son import SON @@ -48,7 +49,6 @@ from pymongo.errors import (AutoReconnect, OperationFailure, PyMongoError) from pymongo.ismaster import IsMaster -from pymongo.monotonic import time as _time from pymongo.monitoring import (ConnectionCheckOutFailedReason, ConnectionClosedReason) from pymongo.network import (command, @@ -250,7 +250,7 @@ def _raise_connection_failure(address, error, msg_prefix=None): def _cond_wait(condition, deadline): - timeout = deadline - _time() if deadline else None + timeout = deadline - time.monotonic() if deadline else None return condition.wait(timeout) @@ -502,7 +502,7 @@ class SocketInfo(object): self.id = id self.authset = set() self.closed = False - self.last_checkin_time = _time() + self.last_checkin_time = time.monotonic() self.performed_handshake = False self.is_writable = False self.max_wire_version = MAX_WIRE_VERSION @@ -862,14 +862,14 @@ class SocketInfo(object): _add_to_command(command, self.opts.server_api) def update_last_checkin_time(self): - self.last_checkin_time = _time() + self.last_checkin_time = time.monotonic() def update_is_writable(self, is_writable): self.is_writable = is_writable def idle_time_seconds(self): """Seconds since this socket was last checked into its pool.""" - return _time() - self.last_checkin_time + return time.monotonic() - self.last_checkin_time def _raise_connection_failure(self, error): # Catch *all* exceptions from socket methods and close the socket. In @@ -1336,7 +1336,7 @@ class Pool: # Get a free socket or create one. if self.opts.wait_queue_timeout: - deadline = _time() + self.opts.wait_queue_timeout + deadline = time.monotonic() + self.opts.wait_queue_timeout else: deadline = None diff --git a/pymongo/pyopenssl_context.py b/pymongo/pyopenssl_context.py index 906e9db2d..3d5cb933f 100644 --- a/pymongo/pyopenssl_context.py +++ b/pymongo/pyopenssl_context.py @@ -18,6 +18,7 @@ context. import socket as _socket import ssl as _stdlibssl +import time from errno import EINTR as _EINTR @@ -33,7 +34,6 @@ from service_identity import ( VerificationError as _SIVerificationError) from pymongo.errors import CertificateError as _CertificateError -from pymongo.monotonic import time as _time from pymongo.ocsp_support import ( _load_trusted_ca_certs, _ocsp_callback) @@ -98,14 +98,14 @@ class _sslConn(_SSL.Connection): def _call(self, call, *args, **kwargs): timeout = self.gettimeout() if timeout: - start = _time() + start = time.monotonic() while True: try: return call(*args, **kwargs) except _RETRY_ERRORS: self.socket_checker.select( self, True, True, timeout) - if timeout and _time() - start > timeout: + if timeout and time.monotonic() - start > timeout: raise _socket.timeout("timed out") continue diff --git a/pymongo/server_description.py b/pymongo/server_description.py index 4a1fe3860..897faa3d3 100644 --- a/pymongo/server_description.py +++ b/pymongo/server_description.py @@ -14,10 +14,11 @@ """Represent one server the driver is connected to.""" +import time + from bson import EPOCH_NAIVE from pymongo.server_type import SERVER_TYPE from pymongo.ismaster import IsMaster -from pymongo.monotonic import time as _time class ServerDescription(object): @@ -67,7 +68,7 @@ class ServerDescription(object): self._ls_timeout_minutes = ismaster.logical_session_timeout_minutes self._round_trip_time = round_trip_time self._me = ismaster.me - self._last_update_time = _time() + self._last_update_time = time.monotonic() self._error = error self._topology_version = ismaster.topology_version if error: diff --git a/pymongo/topology.py b/pymongo/topology.py index a3465597d..194884a75 100644 --- a/pymongo/topology.py +++ b/pymongo/topology.py @@ -18,17 +18,14 @@ import os import queue import random import threading +import time import warnings import weakref from pymongo import (common, helpers, periodic_executor) -from pymongo.pool import PoolOptions -from pymongo.topology_description import (updated_topology_description, - _updated_topology_description_srv_polling, - TopologyDescription, - SRV_POLLING_TOPOLOGIES, TOPOLOGY_TYPE) +from pymongo.client_session import _ServerSessionPool from pymongo.errors import (ConnectionFailure, ConfigurationError, NetworkTimeout, @@ -37,7 +34,7 @@ from pymongo.errors import (ConnectionFailure, PyMongoError, ServerSelectionTimeoutError) from pymongo.monitor import SrvMonitor -from pymongo.monotonic import time as _time +from pymongo.pool import PoolOptions from pymongo.server import Server from pymongo.server_description import ServerDescription from pymongo.server_selectors import (any_server_selector, @@ -46,7 +43,10 @@ from pymongo.server_selectors import (any_server_selector, readable_server_selector, writable_server_selector, Selection) -from pymongo.client_session import _ServerSessionPool +from pymongo.topology_description import (updated_topology_description, + _updated_topology_description_srv_polling, + TopologyDescription, + SRV_POLLING_TOPOLOGIES, TOPOLOGY_TYPE) def process_events_queue(queue_ref): @@ -200,7 +200,7 @@ class Topology(object): def _select_servers_loop(self, selector, timeout, address): """select_servers() guts. Hold the lock when calling this.""" - now = _time() + now = time.monotonic() end_time = now + timeout server_descriptions = self._description.apply_selector( selector, address, custom_selector=self._settings.server_selector) @@ -221,7 +221,7 @@ class Topology(object): # held the lock until now. self._condition.wait(common.MIN_HEARTBEAT_INTERVAL) self._description.check_compatible() - now = _time() + now = time.monotonic() server_descriptions = self._description.apply_selector( selector, address, custom_selector=self._settings.server_selector) diff --git a/test/performance/perf_test.py b/test/performance/perf_test.py index 68baadecb..4a2ba2fea 100644 --- a/test/performance/perf_test.py +++ b/test/performance/perf_test.py @@ -18,6 +18,7 @@ import multiprocessing as mp import os import sys import tempfile +import time import warnings try: @@ -31,7 +32,6 @@ from bson import decode, encode from bson.json_util import loads from gridfs import GridFSBucket from pymongo import MongoClient -from pymongo.monotonic import time from test import client_context, host, port, unittest NUM_ITERATIONS = 100 @@ -59,11 +59,11 @@ def tearDownModule(): class Timer(object): def __enter__(self): - self.start = time() + self.start = time.monotonic() return self def __exit__(self, *args): - self.end = time() + self.end = time.monotonic() self.interval = self.end - self.start @@ -107,10 +107,10 @@ class PerformanceTest(object): def runTest(self): results = [] - start = time() + start = time.monotonic() self.max_iterations = NUM_ITERATIONS for i in range(NUM_ITERATIONS): - if time() - start > MAX_ITERATION_TIME: + if time.monotonic() - start > MAX_ITERATION_TIME: warnings.warn('Test timed out, completed %s iterations.' % i) break self.before() diff --git a/test/test_client.py b/test/test_client.py index ad628a224..b5add1df7 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -53,7 +53,6 @@ from pymongo.errors import (AutoReconnect, from pymongo.monitoring import (ServerHeartbeatListener, ServerHeartbeatStartedEvent) from pymongo.mongo_client import MongoClient -from pymongo.monotonic import time as monotonic_time from pymongo.driver_info import DriverInfo from pymongo.pool import SocketInfo, _METADATA from pymongo.read_preferences import ReadPreference @@ -1549,9 +1548,9 @@ class TestClient(IntegrationTest): # Assert that application operations do not block. for _ in range(10): - start = monotonic_time() + start = time.monotonic() client.admin.command('ping') - total = monotonic_time() - start + total = time.monotonic() - start # Each ping command should not take more than 2 seconds self.assertLess(total, 2) diff --git a/test/test_monotonic.py b/test/test_monotonic.py deleted file mode 100644 index 411a25abc..000000000 --- a/test/test_monotonic.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2018-present MongoDB, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Test the monotonic module.""" - -import sys - -sys.path[0:0] = [""] - -from pymongo.monotonic import time as pymongo_time - -from test import unittest - - -class TestMonotonic(unittest.TestCase): - def test_monotonic_time(self): - try: - from monotonic import monotonic - self.assertIs(monotonic, pymongo_time) - except ImportError: - if sys.version_info[:2] >= (3, 3): - from time import monotonic - self.assertIs(monotonic, pymongo_time) - else: - from time import time - self.assertIs(time, pymongo_time) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/test_session.py b/test/test_session.py index 0773d8a8c..dbd3be5a2 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -17,6 +17,7 @@ import copy import os import sys +import time from io import BytesIO @@ -27,7 +28,6 @@ from pymongo.common import _MAX_END_SESSIONS from pymongo.errors import (ConfigurationError, InvalidOperation, OperationFailure) -from pymongo.monotonic import time as _time from pymongo.read_concern import ReadConcern from test import IntegrationTest, client_context, db_user, db_pwd, unittest, SkipTest from test.utils import (ignore_deprecations, @@ -108,7 +108,7 @@ class TestSession(IntegrationTest): for f, args, kw in ops: with client.start_session() as s: last_use = s._server_session.last_use - start = _time() + start = time.monotonic() self.assertLessEqual(last_use, start) listener.results.clear() # In case "f" modifies its inputs.