PYTHON-2503 Always use time.monotonic

For monotonic time needs.
This commit is contained in:
Bernie Hackett 2021-01-25 12:46:54 -08:00
parent e01d9a37e7
commit cf877e95c7
13 changed files with 50 additions and 131 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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.