diff --git a/pymongo/monitor.py b/pymongo/monitor.py index 81502591bf..27f9bbb992 100644 --- a/pymongo/monitor.py +++ b/pymongo/monitor.py @@ -18,6 +18,8 @@ import threading import weakref +from bson.py3compat import PY3 + from pymongo import common, periodic_executor from pymongo.errors import (NotMasterError, OperationFailure, @@ -30,6 +32,14 @@ from pymongo.srv_resolver import _SrvResolver +def _sanitize(error): + """PYTHON-2433 Clear error traceback info.""" + if PY3: + error.__traceback__ = None + error.__context__ = None + error.__cause__ = None + + class MonitorBase(object): def __init__(self, topology, name, interval, min_interval): """Base class to do periodic work on a background thread. @@ -169,6 +179,7 @@ def _run(self): try: self._server_description = self._check_server() except _OperationCancelled as exc: + _sanitize(exc) # Already closed the connection, wait for the next check. self._server_description = ServerDescription( self._server_description.address, error=exc) @@ -212,6 +223,7 @@ def _check_server(self): except ReferenceError: raise except Exception as error: + _sanitize(error) sd = self._server_description address = sd.address duration = _time() - start diff --git a/test/test_client.py b/test/test_client.py index 19ea1375c2..dc21b357fc 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -57,6 +57,7 @@ from pymongo.driver_info import DriverInfo from pymongo.pool import SocketInfo, _METADATA from pymongo.read_preferences import ReadPreference +from pymongo.server_description import ServerDescription from pymongo.server_selectors import (any_server_selector, writable_server_selector) from pymongo.server_type import SERVER_TYPE @@ -1614,6 +1615,33 @@ def test_direct_connection(self): with self.assertRaises(ConfigurationError): MongoClient(['host1', 'host2'], directConnection=True) + def test_continuous_network_errors(self): + def server_description_count(): + i = 0 + for obj in gc.get_objects(): + try: + if isinstance(obj, ServerDescription): + i += 1 + except ReferenceError: + pass + return i + gc.collect() + with client_knobs(min_heartbeat_interval=0.003): + client = MongoClient( + 'invalid:27017', + heartbeatFrequencyMS=3, + serverSelectionTimeoutMS=100) + initial_count = server_description_count() + self.addCleanup(client.close) + with self.assertRaises(ServerSelectionTimeoutError): + client.test.test.find_one() + gc.collect() + final_count = server_description_count() + # If a bug like PYTHON-2433 is reintroduced then too many + # ServerDescriptions will be kept alive and this test will fail: + # AssertionError: 4 != 22 within 5 delta (18 difference) + self.assertAlmostEqual(initial_count, final_count, delta=5) + class TestExhaustCursor(IntegrationTest): """Test that clients properly handle errors from exhaust cursors."""