Skip to content

Commit 3d936d5

Browse files
morottirmmancom
andauthored
PYTHON-4600 Handle round trip time being negative when time.monotonic() is not monotonic (mongodb#1758)
Co-authored-by: rmorotti <[email protected]>
1 parent f7da117 commit 3d936d5

File tree

5 files changed

+29
-14
lines changed

5 files changed

+29
-14
lines changed

doc/contributors.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,4 @@ The following is a list of people who have contributed to
101101
- Casey Clements (caseyclements)
102102
- Ivan Lukyanchikov (ilukyanchikov)
103103
- Terry Patterson
104+
- Romain Morotti

pymongo/_csot.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,7 @@ def __init__(self) -> None:
149149

150150
def add_sample(self, sample: float) -> None:
151151
if sample < 0:
152-
# Likely system time change while waiting for hello response
153-
# and not using time.monotonic. Ignore it, the next one will
154-
# probably be valid.
155-
return
152+
raise ValueError(f"duration cannot be negative {sample}")
156153
self.samples.append(sample)
157154

158155
def get(self) -> float:

pymongo/asynchronous/monitor.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ def _sanitize(error: Exception) -> None:
4848
error.__cause__ = None
4949

5050

51+
def _monotonic_duration(start: float) -> float:
52+
"""Return the duration since the given start time.
53+
54+
Accounts for buggy platforms where time.monotonic() is not monotonic.
55+
See PYTHON-4600.
56+
"""
57+
return max(0.0, time.monotonic() - start)
58+
59+
5160
class MonitorBase:
5261
def __init__(self, topology: Topology, name: str, interval: int, min_interval: float):
5362
"""Base class to do periodic work on a background thread.
@@ -247,7 +256,7 @@ async def _check_server(self) -> ServerDescription:
247256
_sanitize(error)
248257
sd = self._server_description
249258
address = sd.address
250-
duration = time.monotonic() - start
259+
duration = _monotonic_duration(start)
251260
if self._publish:
252261
awaited = bool(self._stream and sd.is_server_type_known and sd.topology_version)
253262
assert self._listeners is not None
@@ -317,7 +326,8 @@ async def _check_with_socket(self, conn: AsyncConnection) -> tuple[Hello, float]
317326
else:
318327
# New connection handshake or polling hello (MongoDB <4.4).
319328
response = await conn._hello(cluster_time, None, None)
320-
return response, time.monotonic() - start
329+
duration = _monotonic_duration(start)
330+
return response, duration
321331

322332

323333
class SrvMonitor(MonitorBase):
@@ -441,7 +451,7 @@ async def _ping(self) -> float:
441451
raise Exception("_RttMonitor closed")
442452
start = time.monotonic()
443453
await conn.hello()
444-
return time.monotonic() - start
454+
return _monotonic_duration(start)
445455

446456

447457
# Close monitors to cancel any in progress streaming checks before joining

pymongo/read_preferences.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,10 +607,7 @@ def __init__(self) -> None:
607607

608608
def add_sample(self, sample: float) -> None:
609609
if sample < 0:
610-
# Likely system time change while waiting for hello response
611-
# and not using time.monotonic. Ignore it, the next one will
612-
# probably be valid.
613-
return
610+
raise ValueError(f"duration cannot be negative {sample}")
614611
if self.average is None:
615612
self.average = sample
616613
else:

pymongo/synchronous/monitor.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ def _sanitize(error: Exception) -> None:
4848
error.__cause__ = None
4949

5050

51+
def _monotonic_duration(start: float) -> float:
52+
"""Return the duration since the given start time.
53+
54+
Accounts for buggy platforms where time.monotonic() is not monotonic.
55+
See PYTHON-4600.
56+
"""
57+
return max(0.0, time.monotonic() - start)
58+
59+
5160
class MonitorBase:
5261
def __init__(self, topology: Topology, name: str, interval: int, min_interval: float):
5362
"""Base class to do periodic work on a background thread.
@@ -247,7 +256,7 @@ def _check_server(self) -> ServerDescription:
247256
_sanitize(error)
248257
sd = self._server_description
249258
address = sd.address
250-
duration = time.monotonic() - start
259+
duration = _monotonic_duration(start)
251260
if self._publish:
252261
awaited = bool(self._stream and sd.is_server_type_known and sd.topology_version)
253262
assert self._listeners is not None
@@ -317,7 +326,8 @@ def _check_with_socket(self, conn: Connection) -> tuple[Hello, float]:
317326
else:
318327
# New connection handshake or polling hello (MongoDB <4.4).
319328
response = conn._hello(cluster_time, None, None)
320-
return response, time.monotonic() - start
329+
duration = _monotonic_duration(start)
330+
return response, duration
321331

322332

323333
class SrvMonitor(MonitorBase):
@@ -441,7 +451,7 @@ def _ping(self) -> float:
441451
raise Exception("_RttMonitor closed")
442452
start = time.monotonic()
443453
conn.hello()
444-
return time.monotonic() - start
454+
return _monotonic_duration(start)
445455

446456

447457
# Close monitors to cancel any in progress streaming checks before joining

0 commit comments

Comments
 (0)