diff --git a/pymongo/asynchronous/helpers.py b/pymongo/asynchronous/helpers.py index 2c01c19b7a..16b007c38f 100644 --- a/pymongo/asynchronous/helpers.py +++ b/pymongo/asynchronous/helpers.py @@ -30,11 +30,11 @@ ) from pymongo import _csot +from pymongo.common import MAX_ADAPTIVE_RETRIES from pymongo.errors import ( OperationFailure, ) from pymongo.helpers_shared import _REAUTHENTICATION_REQUIRED_CODE -from pymongo.lock import _async_create_lock _IS_SYNC = False @@ -76,11 +76,8 @@ async def inner(*args: Any, **kwargs: Any) -> Any: return cast(F, inner) -_MAX_RETRIES = 5 _BACKOFF_INITIAL = 0.1 _BACKOFF_MAX = 10 -DEFAULT_RETRY_TOKEN_CAPACITY = 1000.0 -DEFAULT_RETRY_TOKEN_RETURN = 0.1 def _backoff( @@ -90,79 +87,32 @@ def _backoff( return jitter * min(initial_delay * (2**attempt), max_delay) -class _TokenBucket: - """A token bucket implementation for rate limiting.""" - - def __init__( - self, - capacity: float = DEFAULT_RETRY_TOKEN_CAPACITY, - return_rate: float = DEFAULT_RETRY_TOKEN_RETURN, - ): - self.lock = _async_create_lock() - self.capacity = capacity - self.tokens = capacity - self.return_rate = return_rate - - async def consume(self) -> bool: - """Consume a token from the bucket if available.""" - async with self.lock: - if self.tokens >= 1: - self.tokens -= 1 - return True - return False - - async def deposit(self, retry: bool = False) -> None: - """Deposit a token back into the bucket.""" - retry_token = 1 if retry else 0 - async with self.lock: - self.tokens = min(self.capacity, self.tokens + retry_token + self.return_rate) - - class _RetryPolicy: - """A retry limiter that performs exponential backoff with jitter. - - When adaptive retries are enabled, retry attempts are limited by a token bucket to prevent overwhelming the server during - a prolonged outage or high load. - """ + """A retry limiter that performs exponential backoff with jitter.""" def __init__( self, - token_bucket: _TokenBucket, - attempts: int = _MAX_RETRIES, + attempts: int = MAX_ADAPTIVE_RETRIES, backoff_initial: float = _BACKOFF_INITIAL, backoff_max: float = _BACKOFF_MAX, - adaptive_retry: bool = False, ): - self.token_bucket = token_bucket self.attempts = attempts self.backoff_initial = backoff_initial self.backoff_max = backoff_max - self.adaptive_retry = adaptive_retry - - async def record_success(self, retry: bool) -> None: - """Record a successful operation.""" - if self.adaptive_retry: - await self.token_bucket.deposit(retry) def backoff(self, attempt: int) -> float: - """Return the backoff duration for the given .""" + """Return the backoff duration for the given attempt.""" return _backoff(max(0, attempt - 1), self.backoff_initial, self.backoff_max) async def should_retry(self, attempt: int, delay: float) -> bool: - """Return if we have budget to retry and how long to backoff.""" + """Return if we have retry attempts remaining and the next backoff would not exceed a timeout.""" if attempt > self.attempts: return False - # If the delay would exceed the deadline, bail early before consuming a token. if _csot.get_timeout(): if time.monotonic() + delay > _csot.get_deadline(): return False - # Check token bucket last since we only want to consume a token if we actually retry. - if self.adaptive_retry and not await self.token_bucket.consume(): - # DRIVERS-3246 Improve diagnostics when this case happens. - # We could add info to the exception and log. - return False return True diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 8936068af6..9ba305fd0b 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -70,7 +70,6 @@ from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.asynchronous.helpers import ( _RetryPolicy, - _TokenBucket, ) from pymongo.asynchronous.settings import TopologySettings from pymongo.asynchronous.topology import Topology, _ErrorContext @@ -615,17 +614,17 @@ def __init__( client to use Stable API. See `versioned API `_ for details. - | **Adaptive retry options:** - | (If not enabled explicitly, adaptive retries will not be enabled.) + | **Overload retry options:** - - `adaptive_retries`: (boolean) Whether the adaptive retry mechanism is enabled for this client. - If enabled, server overload errors will use a token-bucket based system to mitigate further overload. + - `max_adaptive_retries`: (int) How many retries to allow for overload errors. Defaults to ``2``. + - `enable_overload_retargeting`: (boolean) Whether overload retargeting is enabled for this client. + If enabled, server overload errors will cause retry attempts to select a server that has not yet returned an overload error, if possible. Defaults to ``False``. .. seealso:: The MongoDB documentation on `connections `_. .. versionchanged:: 4.17 - Added the ``adaptive_retries`` URI and keyword argument. + Added the ``max_adaptive_retries`` and ``enable_overload_retargeting`` URI and keyword arguments. .. versionchanged:: 4.5 Added the ``serverMonitoringMode`` keyword argument. @@ -894,9 +893,7 @@ def __init__( self._options.read_concern, ) - self._retry_policy = _RetryPolicy( - _TokenBucket(), adaptive_retry=self._options.adaptive_retries - ) + self._retry_policy = _RetryPolicy(attempts=self._options.max_adaptive_retries) self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name) @@ -2822,7 +2819,6 @@ async def run(self) -> T: self._check_last_error(check_csot=True) try: res = await self._read() if self._is_read else await self._write() - await self._retry_policy.record_success(self._attempt_number > 0) # Track whether the transaction has completed a command. # If we need to apply backpressure to the first command, # we will need to revert back to starting state. @@ -2930,10 +2926,9 @@ async def run(self) -> T: transaction.set_starting() transaction.attempt = 0 - if ( - self._server is not None - and self._client.topology_description.topology_type_name == "Sharded" - or exc.has_error_label("SystemOverloadedError") + if self._server is not None and ( + self._client.topology_description.topology_type_name == "Sharded" + or (overloaded and self._client.options.enable_overload_retargeting) ): self._deprioritized_servers.append(self._server) diff --git a/pymongo/client_options.py b/pymongo/client_options.py index 1e488c2b8f..e5dc609946 100644 --- a/pymongo/client_options.py +++ b/pymongo/client_options.py @@ -235,10 +235,15 @@ def __init__( self.__server_monitoring_mode = options.get( "servermonitoringmode", common.SERVER_MONITORING_MODE ) - self.__adaptive_retries = ( - options.get("adaptive_retries", common.ADAPTIVE_RETRIES) - if "adaptive_retries" in options - else options.get("adaptiveretries", common.ADAPTIVE_RETRIES) + self.__max_adaptive_retries = ( + options.get("max_adaptive_retries", common.MAX_ADAPTIVE_RETRIES) + if "max_adaptive_retries" in options + else options.get("maxadaptiveretries", common.MAX_ADAPTIVE_RETRIES) + ) + self.__enable_overload_retargeting = ( + options.get("enable_overload_retargeting", common.ENABLE_OVERLOAD_RETARGETING) + if "enable_overload_retargeting" in options + else options.get("enableoverloadretargeting", common.ENABLE_OVERLOAD_RETARGETING) ) @property @@ -353,9 +358,17 @@ def server_monitoring_mode(self) -> str: return self.__server_monitoring_mode @property - def adaptive_retries(self) -> bool: - """The configured adaptiveRetries option. + def max_adaptive_retries(self) -> int: + """The configured maxAdaptiveRetries option. + + .. versionadded:: 4.17 + """ + return self.__max_adaptive_retries + + @property + def enable_overload_retargeting(self) -> bool: + """The configured enableOverloadRetargeting option. - .. versionadded:: 4.XX + .. versionadded:: 4.17 """ - return self.__adaptive_retries + return self.__enable_overload_retargeting diff --git a/pymongo/common.py b/pymongo/common.py index 8b9797682f..5fe7eb62e8 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -140,8 +140,11 @@ # Default value for serverMonitoringMode SERVER_MONITORING_MODE = "auto" # poll/stream/auto -# Default value for adaptiveRetries -ADAPTIVE_RETRIES = False +# Default value for max adaptive retries +MAX_ADAPTIVE_RETRIES = 2 + +# Default value for enableOverloadRetargeting +ENABLE_OVERLOAD_RETARGETING = False # Auth mechanism properties that must raise an error instead of warning if they invalidate. _MECH_PROP_MUST_RAISE = ["CANONICALIZE_HOST_NAME"] @@ -741,7 +744,8 @@ def validate_server_monitoring_mode(option: str, value: str) -> str: "srvmaxhosts": validate_non_negative_integer, "timeoutms": validate_timeoutms, "servermonitoringmode": validate_server_monitoring_mode, - "adaptiveretries": validate_boolean_or_string, + "maxadaptiveretries": validate_non_negative_integer, + "enableoverloadretargeting": validate_boolean_or_string, } # Dictionary where keys are the names of URI options specific to pymongo, @@ -775,7 +779,8 @@ def validate_server_monitoring_mode(option: str, value: str) -> str: "server_selector": validate_is_callable_or_none, "auto_encryption_opts": validate_auto_encryption_opts_or_none, "authoidcallowedhosts": validate_list, - "adaptive_retries": validate_boolean_or_string, + "max_adaptive_retries": validate_non_negative_integer, + "enable_overload_retargeting": validate_boolean_or_string, } # Dictionary where keys are any URI option name, and values are the diff --git a/pymongo/synchronous/helpers.py b/pymongo/synchronous/helpers.py index 1a27fc11a5..bbe8963fe7 100644 --- a/pymongo/synchronous/helpers.py +++ b/pymongo/synchronous/helpers.py @@ -30,11 +30,11 @@ ) from pymongo import _csot +from pymongo.common import MAX_ADAPTIVE_RETRIES from pymongo.errors import ( OperationFailure, ) from pymongo.helpers_shared import _REAUTHENTICATION_REQUIRED_CODE -from pymongo.lock import _create_lock _IS_SYNC = True @@ -76,11 +76,8 @@ def inner(*args: Any, **kwargs: Any) -> Any: return cast(F, inner) -_MAX_RETRIES = 5 _BACKOFF_INITIAL = 0.1 _BACKOFF_MAX = 10 -DEFAULT_RETRY_TOKEN_CAPACITY = 1000.0 -DEFAULT_RETRY_TOKEN_RETURN = 0.1 def _backoff( @@ -90,79 +87,32 @@ def _backoff( return jitter * min(initial_delay * (2**attempt), max_delay) -class _TokenBucket: - """A token bucket implementation for rate limiting.""" - - def __init__( - self, - capacity: float = DEFAULT_RETRY_TOKEN_CAPACITY, - return_rate: float = DEFAULT_RETRY_TOKEN_RETURN, - ): - self.lock = _create_lock() - self.capacity = capacity - self.tokens = capacity - self.return_rate = return_rate - - def consume(self) -> bool: - """Consume a token from the bucket if available.""" - with self.lock: - if self.tokens >= 1: - self.tokens -= 1 - return True - return False - - def deposit(self, retry: bool = False) -> None: - """Deposit a token back into the bucket.""" - retry_token = 1 if retry else 0 - with self.lock: - self.tokens = min(self.capacity, self.tokens + retry_token + self.return_rate) - - class _RetryPolicy: - """A retry limiter that performs exponential backoff with jitter. - - When adaptive retries are enabled, retry attempts are limited by a token bucket to prevent overwhelming the server during - a prolonged outage or high load. - """ + """A retry limiter that performs exponential backoff with jitter.""" def __init__( self, - token_bucket: _TokenBucket, - attempts: int = _MAX_RETRIES, + attempts: int = MAX_ADAPTIVE_RETRIES, backoff_initial: float = _BACKOFF_INITIAL, backoff_max: float = _BACKOFF_MAX, - adaptive_retry: bool = False, ): - self.token_bucket = token_bucket self.attempts = attempts self.backoff_initial = backoff_initial self.backoff_max = backoff_max - self.adaptive_retry = adaptive_retry - - def record_success(self, retry: bool) -> None: - """Record a successful operation.""" - if self.adaptive_retry: - self.token_bucket.deposit(retry) def backoff(self, attempt: int) -> float: - """Return the backoff duration for the given .""" + """Return the backoff duration for the given attempt.""" return _backoff(max(0, attempt - 1), self.backoff_initial, self.backoff_max) def should_retry(self, attempt: int, delay: float) -> bool: - """Return if we have budget to retry and how long to backoff.""" + """Return if we have retry attempts remaining and the next backoff would not exceed a timeout.""" if attempt > self.attempts: return False - # If the delay would exceed the deadline, bail early before consuming a token. if _csot.get_timeout(): if time.monotonic() + delay > _csot.get_deadline(): return False - # Check token bucket last since we only want to consume a token if we actually retry. - if self.adaptive_retry and not self.token_bucket.consume(): - # DRIVERS-3246 Improve diagnostics when this case happens. - # We could add info to the exception and log. - return False return True diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index ab74650780..769fbcb84b 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -113,7 +113,6 @@ from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.helpers import ( _RetryPolicy, - _TokenBucket, ) from pymongo.synchronous.settings import TopologySettings from pymongo.synchronous.topology import Topology, _ErrorContext @@ -615,17 +614,17 @@ def __init__( client to use Stable API. See `versioned API `_ for details. - | **Adaptive retry options:** - | (If not enabled explicitly, adaptive retries will not be enabled.) + | **Overload retry options:** - - `adaptive_retries`: (boolean) Whether the adaptive retry mechanism is enabled for this client. - If enabled, server overload errors will use a token-bucket based system to mitigate further overload. + - `max_adaptive_retries`: (int) How many retries to allow for overload errors. Defaults to ``2``. + - `enable_overload_retargeting`: (boolean) Whether overload retargeting is enabled for this client. + If enabled, server overload errors will cause retry attempts to select a server that has not yet returned an overload error, if possible. Defaults to ``False``. .. seealso:: The MongoDB documentation on `connections `_. .. versionchanged:: 4.17 - Added the ``adaptive_retries`` URI and keyword argument. + Added the ``max_adaptive_retries`` and ``enable_overload_retargeting`` URI and keyword arguments. .. versionchanged:: 4.5 Added the ``serverMonitoringMode`` keyword argument. @@ -894,9 +893,7 @@ def __init__( self._options.read_concern, ) - self._retry_policy = _RetryPolicy( - _TokenBucket(), adaptive_retry=self._options.adaptive_retries - ) + self._retry_policy = _RetryPolicy(attempts=self._options.max_adaptive_retries) self._init_based_on_options(self._seeds, srv_max_hosts, srv_service_name) @@ -2812,7 +2809,6 @@ def run(self) -> T: self._check_last_error(check_csot=True) try: res = self._read() if self._is_read else self._write() - self._retry_policy.record_success(self._attempt_number > 0) # Track whether the transaction has completed a command. # If we need to apply backpressure to the first command, # we will need to revert back to starting state. @@ -2920,10 +2916,9 @@ def run(self) -> T: transaction.set_starting() transaction.attempt = 0 - if ( - self._server is not None - and self._client.topology_description.topology_type_name == "Sharded" - or exc.has_error_label("SystemOverloadedError") + if self._server is not None and ( + self._client.topology_description.topology_type_name == "Sharded" + or (overloaded and self._client.options.enable_overload_retargeting) ): self._deprioritized_servers.append(self._server) diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index f3cee70e15..68765244fb 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -652,20 +652,37 @@ async def test_detected_environment_warning(self, mock_get_hosts): with self.assertWarns(UserWarning): self.simple_client(multi_host) - async def test_adaptive_retries(self): - # Assert that adaptive retries are disabled by default. + async def test_max_adaptive_retries(self): + # Assert that max adaptive retries default to 2. c = self.simple_client(connect=False) - self.assertFalse(c.options.adaptive_retries) + self.assertEqual(c.options.max_adaptive_retries, 2) - # Assert that adaptive retries can be enabled through connection or client options. - c = self.simple_client(connect=False, adaptive_retries=True) - self.assertTrue(c.options.adaptive_retries) + # Assert that max adaptive retries can be configured through connection or client options. + c = self.simple_client(connect=False, max_adaptive_retries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) - c = self.simple_client(connect=False, adaptiveRetries=True) - self.assertTrue(c.options.adaptive_retries) + c = self.simple_client(connect=False, maxAdaptiveRetries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) - c = self.simple_client(host="mongodb://localhost/?adaptiveretries=true", connect=False) - self.assertTrue(c.options.adaptive_retries) + c = self.simple_client(host="mongodb://localhost/?maxAdaptiveRetries=10", connect=False) + self.assertEqual(c.options.max_adaptive_retries, 10) + + async def test_enable_overload_retargeting(self): + # Assert that overload retargeting defaults to false. + c = self.simple_client(connect=False) + self.assertFalse(c.options.enable_overload_retargeting) + + # Assert that overload retargeting can be enabled through connection or client options. + c = self.simple_client(connect=False, enable_overload_retargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client(connect=False, enableOverloadRetargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client( + host="mongodb://localhost/?enableOverloadRetargeting=true", connect=False + ) + self.assertTrue(c.options.enable_overload_retargeting) class TestClient(AsyncIntegrationTest): diff --git a/test/asynchronous/test_client_backpressure.py b/test/asynchronous/test_client_backpressure.py index d79a9114dc..21ccc34f30 100644 --- a/test/asynchronous/test_client_backpressure.py +++ b/test/asynchronous/test_client_backpressure.py @@ -21,20 +21,18 @@ from time import perf_counter from unittest.mock import patch +from pymongo.common import MAX_ADAPTIVE_RETRIES + sys.path[0:0] = [""] from test.asynchronous import ( AsyncIntegrationTest, - AsyncPyMongoTestCase, async_client_context, unittest, ) from test.asynchronous.unified_format import generate_test_classes from test.utils_shared import EventListener, OvertCommandListener -import pymongo -from pymongo.asynchronous import helpers -from pymongo.asynchronous.helpers import _MAX_RETRIES, _RetryPolicy, _TokenBucket from pymongo.errors import OperationFailure, PyMongoError _IS_SYNC = False @@ -60,13 +58,13 @@ async def test_retry_overload_error_command(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} async with self.fail_point(fail_many): await self.db.command("find", "t") - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} async with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: await self.db.command("find", "t") @@ -80,13 +78,13 @@ async def test_retry_overload_error_find(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} async with self.fail_point(fail_many): await self.db.t.find_one() - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} async with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: await self.db.t.find_one() @@ -98,13 +96,13 @@ async def test_retry_overload_error_find(self): async def test_retry_overload_error_insert_one(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} async with self.fail_point(fail_many): await self.db.t.insert_one({"x": 1}) - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} async with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: await self.db.t.insert_one({"x": 1}) @@ -120,13 +118,13 @@ async def test_retry_overload_error_update_many(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} async with self.fail_point(fail_many): await self.db.t.update_many({}, {"$set": {"x": 2}}) - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} async with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: await self.db.t.update_many({}, {"$set": {"x": 2}}) @@ -142,7 +140,7 @@ async def test_retry_overload_error_getMore(self): # Ensure command is retried on overload error. fail_many = { "configureFailPoint": "failCommand", - "mode": {"times": _MAX_RETRIES}, + "mode": {"times": MAX_ADAPTIVE_RETRIES}, "data": { "failCommands": ["getMore"], "errorCode": 462, # IngressRequestRateLimitExceeded @@ -154,9 +152,9 @@ async def test_retry_overload_error_getMore(self): async with self.fail_point(fail_many): await cursor.to_list() - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = fail_many.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} cursor = coll.find(batch_size=2) await cursor.next() async with self.fail_point(fail_too_many): @@ -167,50 +165,6 @@ async def test_retry_overload_error_getMore(self): self.assertIn("SystemOverloadedError", str(error.exception)) -class TestRetryPolicy(AsyncPyMongoTestCase): - async def test_retry_policy(self): - capacity = 10 - retry_policy = _RetryPolicy(_TokenBucket(capacity=capacity), adaptive_retry=True) - self.assertEqual(retry_policy.attempts, helpers._MAX_RETRIES) - self.assertEqual(retry_policy.backoff_initial, helpers._BACKOFF_INITIAL) - self.assertEqual(retry_policy.backoff_max, helpers._BACKOFF_MAX) - for i in range(1, helpers._MAX_RETRIES + 1): - self.assertTrue(await retry_policy.should_retry(i, 0)) - self.assertFalse(await retry_policy.should_retry(helpers._MAX_RETRIES + 1, 0)) - for i in range(capacity - helpers._MAX_RETRIES): - self.assertTrue(await retry_policy.should_retry(1, 0)) - # No tokens left, should not retry. - self.assertFalse(await retry_policy.should_retry(1, 0)) - self.assertEqual(retry_policy.token_bucket.tokens, 0) - - # record_success should generate tokens. - for _ in range(int(2 / helpers.DEFAULT_RETRY_TOKEN_RETURN)): - await retry_policy.record_success(retry=False) - self.assertAlmostEqual(retry_policy.token_bucket.tokens, 2) - for i in range(2): - self.assertTrue(await retry_policy.should_retry(1, 0)) - self.assertFalse(await retry_policy.should_retry(1, 0)) - - # Recording a successful retry should return 1 additional token. - await retry_policy.record_success(retry=True) - self.assertAlmostEqual( - retry_policy.token_bucket.tokens, 1 + helpers.DEFAULT_RETRY_TOKEN_RETURN - ) - self.assertTrue(await retry_policy.should_retry(1, 0)) - self.assertFalse(await retry_policy.should_retry(1, 0)) - self.assertAlmostEqual(retry_policy.token_bucket.tokens, helpers.DEFAULT_RETRY_TOKEN_RETURN) - - async def test_retry_policy_csot(self): - retry_policy = _RetryPolicy(_TokenBucket()) - self.assertTrue(await retry_policy.should_retry(1, 0.5)) - with pymongo.timeout(0.5): - self.assertTrue(await retry_policy.should_retry(1, 0)) - self.assertTrue(await retry_policy.should_retry(1, 0.1)) - # Would exceed the timeout, should not retry. - self.assertFalse(await retry_policy.should_retry(1, 1.0)) - self.assertTrue(await retry_policy.should_retry(1, 1.0)) - - # Prose tests. class AsyncTestClientBackpressure(AsyncIntegrationTest): listener: EventListener @@ -270,14 +224,14 @@ async def test_01_operation_retry_uses_exponential_backoff(self, random_func): await collection.insert_one({"a": 1}) end1 = perf_counter() - # f. Compare the two time between the two runs. - # The sum of 5 backoffs is 3.1 seconds. There is a 1-second window to account for potential variance between the two + # f. Compare the times between the two runs. + # The sum of 2 backoffs is 0.3 seconds. There is a 0.3-second window to account for potential variance between the two # runs. - self.assertTrue(abs((end1 - start1) - (end0 - start0 + 3.1)) < 1) + self.assertTrue(abs((end1 - start1) - (end0 - start0 + 0.3)) < 0.3) @async_client_context.require_failCommand_appName async def test_03_overload_retries_limited(self): - # Drivers should test that without adaptive retries enabled, overload errors are retried a maximum of five times. + # Drivers should test that overload errors are retried a maximum of two times. # 1. Let `client` be a `MongoClient`. client = self.client @@ -300,30 +254,29 @@ async def test_03_overload_retries_limited(self): with self.assertRaises(PyMongoError) as error: await coll.find_one({}) - # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverLoadedError` error labels. + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. self.assertIn("RetryableError", str(error.exception)) self.assertIn("SystemOverloadedError", str(error.exception)) - # 6. Assert that the total number of started commands is MAX_RETRIES + 1. - self.assertEqual(len(self.listener.started_events), _MAX_RETRIES + 1) + # 6. Assert that the total number of started commands is MAX_ADAPTIVE_RETRIES + 1. + self.assertEqual(len(self.listener.started_events), MAX_ADAPTIVE_RETRIES + 1) @async_client_context.require_failCommand_appName - async def test_04_adaptive_retries_limited_by_tokens(self): - # Drivers should test that when enabled, adaptive retries are limited by the number of tokens in the bucket. + async def test_04_overload_retries_limited_configured(self): + # Drivers should test that overload errors are retried a maximum of maxAdaptiveRetries times. + max_retries = 1 - # 1. Let `client` be a `MongoClient` with adaptiveRetries=True. - client = await self.async_rs_or_single_client( - adaptive_retries=True, event_listeners=[self.listener] + # 1. Let `client` be a `MongoClient` with `maxAdaptiveRetries=1` and command event monitoring enabled. + client = await self.async_single_client( + maxAdaptiveRetries=max_retries, event_listeners=[self.listener] ) - # 2. Set `client`'s retry token bucket to have 2 tokens. - client._retry_policy.token_bucket.tokens = 2 - # 3. Let `coll` be a collection. + # 2. Let `coll` be a collection. coll = client.pymongo_test.coll - # 4. Configure the following failpoint: + # 3. Configure the following failpoint: failpoint = { "configureFailPoint": "failCommand", - "mode": {"times": 3}, + "mode": "alwaysOn", "data": { "failCommands": ["find"], "errorCode": 462, # IngressRequestRateLimitExceeded @@ -331,17 +284,17 @@ async def test_04_adaptive_retries_limited_by_tokens(self): }, } - # 5. Perform a find operation with `coll` that fails. + # 4. Perform a find operation with `coll` that fails. async with self.fail_point(failpoint): with self.assertRaises(PyMongoError) as error: await coll.find_one({}) - # 6. Assert that the raised error contains both the `RetryableError` and `SystemOverLoadedError` error labels. + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. self.assertIn("RetryableError", str(error.exception)) self.assertIn("SystemOverloadedError", str(error.exception)) - # 7. Assert that the total number of started commands is 3: one for the initial attempt and two for the retries. - self.assertEqual(len(self.listener.started_events), 3) + # 6. Assert that the total number of started commands is max_retries + 1. + self.assertEqual(len(self.listener.started_events), max_retries + 1) # Location of JSON test specifications. diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 6adfaaae17..259cd9cff5 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -265,14 +265,17 @@ async def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @async_client_context.require_secondaries_count(1) @async_client_context.require_failCommand_fail_point @async_client_context.require_version_min(4, 4, 0) - async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available_and_overload_retargeting_is_enabled( self ): listener = OvertCommandListener() - # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, `enableOverloadRetargeting=True`, and command event monitoring enabled. client = await self.async_rs_or_single_client( - event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + enableOverloadRetargeting=True, ) # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. @@ -339,6 +342,47 @@ async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_o # 6. Assert that both events occurred the same server. assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + @async_client_context.require_replica_set + @async_client_context.require_secondaries_count(1) + @async_client_context.require_failCommand_fail_point + @async_client_context.require_version_min(4, 4, 0) + async def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same_replicaset_server_when_one_is_available_and_overload_retargeting_is_disabled( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = await self.async_rs_or_single_client( + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + await async_set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + await client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() diff --git a/test/client-backpressure/backpressure-connection-checkin.json b/test/client-backpressure/backpressure-connection-checkin.json index 340c87c014..794951ad5f 100644 --- a/test/client-backpressure/backpressure-connection-checkin.json +++ b/test/client-backpressure/backpressure-connection-checkin.json @@ -85,24 +85,6 @@ "client": "client", "eventType": "cmap", "events": [ - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, - { - "connectionCheckedOutEvent": {} - }, - { - "connectionCheckedInEvent": {} - }, { "connectionCheckedOutEvent": {} }, diff --git a/test/client-backpressure/backpressure-retry-loop.json b/test/client-backpressure/backpressure-retry-loop.json index 9121b21856..a0b4877fac 100644 --- a/test/client-backpressure/backpressure-retry-loop.json +++ b/test/client-backpressure/backpressure-retry-loop.json @@ -145,7 +145,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -197,16 +197,6 @@ "commandName": "listDatabases" } }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, { "commandSucceededEvent": { "commandName": "listDatabases" @@ -283,7 +273,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -332,16 +322,6 @@ "commandName": "listDatabases" } }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, { "commandSucceededEvent": { "commandName": "listDatabases" @@ -415,7 +395,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -467,16 +447,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" @@ -558,7 +528,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -620,16 +590,6 @@ "commandName": "bulkWrite" } }, - { - "commandFailedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandStartedEvent": { - "commandName": "bulkWrite" - } - }, { "commandSucceededEvent": { "commandName": "bulkWrite" @@ -721,7 +681,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -780,16 +740,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" @@ -873,7 +823,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -925,16 +875,6 @@ "commandName": "listCollections" } }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, { "commandSucceededEvent": { "commandName": "listCollections" @@ -1011,7 +951,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1063,16 +1003,6 @@ "commandName": "listCollections" } }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, { "commandSucceededEvent": { "commandName": "listCollections" @@ -1149,7 +1079,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1204,16 +1134,6 @@ "commandName": "ping" } }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "ping" - } - }, { "commandSucceededEvent": { "commandName": "ping" @@ -1352,7 +1272,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1404,16 +1324,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" @@ -1490,7 +1400,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1542,16 +1452,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" @@ -1628,7 +1528,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1680,16 +1580,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" @@ -1766,7 +1656,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1815,16 +1705,6 @@ "commandName": "count" } }, - { - "commandFailedEvent": { - "commandName": "count" - } - }, - { - "commandStartedEvent": { - "commandName": "count" - } - }, { "commandSucceededEvent": { "commandName": "count" @@ -1898,7 +1778,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -1951,16 +1831,6 @@ "commandName": "distinct" } }, - { - "commandFailedEvent": { - "commandName": "distinct" - } - }, - { - "commandStartedEvent": { - "commandName": "distinct" - } - }, { "commandSucceededEvent": { "commandName": "distinct" @@ -2038,7 +1908,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2090,16 +1960,6 @@ "commandName": "find" } }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, { "commandSucceededEvent": { "commandName": "find" @@ -2176,7 +2036,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2228,16 +2088,6 @@ "commandName": "find" } }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, { "commandSucceededEvent": { "commandName": "find" @@ -2314,7 +2164,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2363,16 +2213,6 @@ "commandName": "listIndexes" } }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, { "commandSucceededEvent": { "commandName": "listIndexes" @@ -2446,7 +2286,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2495,16 +2335,6 @@ "commandName": "listIndexes" } }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, { "commandSucceededEvent": { "commandName": "listIndexes" @@ -2578,7 +2408,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2630,16 +2460,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" @@ -2716,7 +2536,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2771,16 +2591,6 @@ "commandName": "insert" } }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, { "commandSucceededEvent": { "commandName": "insert" @@ -2860,7 +2670,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -2917,16 +2727,6 @@ "commandName": "insert" } }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, { "commandSucceededEvent": { "commandName": "insert" @@ -3008,7 +2808,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3060,16 +2860,6 @@ "commandName": "delete" } }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, { "commandSucceededEvent": { "commandName": "delete" @@ -3146,7 +2936,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3198,16 +2988,6 @@ "commandName": "delete" } }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, { "commandSucceededEvent": { "commandName": "delete" @@ -3284,7 +3064,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3339,16 +3119,6 @@ "commandName": "update" } }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, { "commandSucceededEvent": { "commandName": "update" @@ -3428,7 +3198,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3485,16 +3255,6 @@ "commandName": "update" } }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, { "commandSucceededEvent": { "commandName": "update" @@ -3576,7 +3336,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3633,16 +3393,6 @@ "commandName": "update" } }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, { "commandSucceededEvent": { "commandName": "update" @@ -3724,7 +3474,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3776,16 +3526,6 @@ "commandName": "findAndModify" } }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, { "commandSucceededEvent": { "commandName": "findAndModify" @@ -3862,7 +3602,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -3917,16 +3657,6 @@ "commandName": "findAndModify" } }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, { "commandSucceededEvent": { "commandName": "findAndModify" @@ -4006,7 +3736,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -4063,16 +3793,6 @@ "commandName": "findAndModify" } }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, { "commandSucceededEvent": { "commandName": "findAndModify" @@ -4154,7 +3874,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -4215,16 +3935,6 @@ "commandName": "insert" } }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, { "commandSucceededEvent": { "commandName": "insert" @@ -4310,7 +4020,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -4365,16 +4075,6 @@ "commandName": "createIndexes" } }, - { - "commandFailedEvent": { - "commandName": "createIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "createIndexes" - } - }, { "commandSucceededEvent": { "commandName": "createIndexes" @@ -4464,7 +4164,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -4516,16 +4216,6 @@ "commandName": "dropIndexes" } }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, { "commandSucceededEvent": { "commandName": "dropIndexes" @@ -4612,7 +4302,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -4661,16 +4351,6 @@ "commandName": "dropIndexes" } }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, { "commandSucceededEvent": { "commandName": "dropIndexes" @@ -4744,7 +4424,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -4800,16 +4480,6 @@ "commandName": "aggregate" } }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, { "commandSucceededEvent": { "commandName": "aggregate" diff --git a/test/client-backpressure/backpressure-retry-max-attempts.json b/test/client-backpressure/backpressure-retry-max-attempts.json index ed2352ca83..de52572765 100644 --- a/test/client-backpressure/backpressure-retry-max-attempts.json +++ b/test/client-backpressure/backpressure-retry-max-attempts.json @@ -1,5 +1,5 @@ { - "description": "tests that operations retry at most maxAttempts=5 times", + "description": "tests that operations retry at most maxAttempts=2 times", "schemaVersion": "1.3", "runOnRequirements": [ { @@ -68,7 +68,7 @@ ], "tests": [ { - "description": "client.listDatabases retries at most maxAttempts=5 times", + "description": "client.listDatabases retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -107,36 +107,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, { "commandStartedEvent": { "commandName": "listDatabases" @@ -172,7 +142,7 @@ ] }, { - "description": "client.listDatabaseNames retries at most maxAttempts=5 times", + "description": "client.listDatabaseNames retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -208,36 +178,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandStartedEvent": { - "commandName": "listDatabases" - } - }, - { - "commandFailedEvent": { - "commandName": "listDatabases" - } - }, { "commandStartedEvent": { "commandName": "listDatabases" @@ -273,7 +213,7 @@ ] }, { - "description": "client.createChangeStream retries at most maxAttempts=5 times", + "description": "client.createChangeStream retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -312,36 +252,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" @@ -377,7 +287,7 @@ ] }, { - "description": "client.clientBulkWrite retries at most maxAttempts=5 times", + "description": "client.clientBulkWrite retries at most maxAttempts=2 times", "runOnRequirements": [ { "minServerVersion": "8.0" @@ -431,36 +341,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandFailedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandStartedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandFailedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandStartedEvent": { - "commandName": "bulkWrite" - } - }, - { - "commandFailedEvent": { - "commandName": "bulkWrite" - } - }, { "commandStartedEvent": { "commandName": "bulkWrite" @@ -496,7 +376,7 @@ ] }, { - "description": "database.aggregate read retries at most maxAttempts=5 times", + "description": "database.aggregate read retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -542,36 +422,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" @@ -607,7 +457,7 @@ ] }, { - "description": "database.listCollections retries at most maxAttempts=5 times", + "description": "database.listCollections retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -646,36 +496,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, { "commandStartedEvent": { "commandName": "listCollections" @@ -711,7 +531,7 @@ ] }, { - "description": "database.listCollectionNames retries at most maxAttempts=5 times", + "description": "database.listCollectionNames retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -750,36 +570,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, - { - "commandStartedEvent": { - "commandName": "listCollections" - } - }, - { - "commandFailedEvent": { - "commandName": "listCollections" - } - }, { "commandStartedEvent": { "commandName": "listCollections" @@ -815,7 +605,7 @@ ] }, { - "description": "database.runCommand retries at most maxAttempts=5 times", + "description": "database.runCommand retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -857,36 +647,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "ping" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "ping" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, - { - "commandStartedEvent": { - "commandName": "ping" - } - }, - { - "commandFailedEvent": { - "commandName": "ping" - } - }, { "commandStartedEvent": { "commandName": "ping" @@ -922,7 +682,7 @@ ] }, { - "description": "database.createChangeStream retries at most maxAttempts=5 times", + "description": "database.createChangeStream retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -961,36 +721,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" @@ -1026,7 +756,7 @@ ] }, { - "description": "collection.aggregate read retries at most maxAttempts=5 times", + "description": "collection.aggregate read retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1065,36 +795,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" @@ -1130,7 +830,7 @@ ] }, { - "description": "collection.countDocuments retries at most maxAttempts=5 times", + "description": "collection.countDocuments retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1169,36 +869,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" @@ -1234,7 +904,7 @@ ] }, { - "description": "collection.estimatedDocumentCount retries at most maxAttempts=5 times", + "description": "collection.estimatedDocumentCount retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1270,36 +940,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "count" - } - }, - { - "commandFailedEvent": { - "commandName": "count" - } - }, - { - "commandStartedEvent": { - "commandName": "count" - } - }, - { - "commandFailedEvent": { - "commandName": "count" - } - }, - { - "commandStartedEvent": { - "commandName": "count" - } - }, - { - "commandFailedEvent": { - "commandName": "count" - } - }, { "commandStartedEvent": { "commandName": "count" @@ -1335,7 +975,7 @@ ] }, { - "description": "collection.distinct retries at most maxAttempts=5 times", + "description": "collection.distinct retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1375,36 +1015,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "distinct" - } - }, - { - "commandFailedEvent": { - "commandName": "distinct" - } - }, - { - "commandStartedEvent": { - "commandName": "distinct" - } - }, - { - "commandFailedEvent": { - "commandName": "distinct" - } - }, - { - "commandStartedEvent": { - "commandName": "distinct" - } - }, - { - "commandFailedEvent": { - "commandName": "distinct" - } - }, { "commandStartedEvent": { "commandName": "distinct" @@ -1440,7 +1050,7 @@ ] }, { - "description": "collection.find retries at most maxAttempts=5 times", + "description": "collection.find retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1479,36 +1089,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, { "commandStartedEvent": { "commandName": "find" @@ -1544,7 +1124,7 @@ ] }, { - "description": "collection.findOne retries at most maxAttempts=5 times", + "description": "collection.findOne retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1583,36 +1163,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandFailedEvent": { - "commandName": "find" - } - }, { "commandStartedEvent": { "commandName": "find" @@ -1648,7 +1198,7 @@ ] }, { - "description": "collection.listIndexes retries at most maxAttempts=5 times", + "description": "collection.listIndexes retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1684,36 +1234,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, { "commandStartedEvent": { "commandName": "listIndexes" @@ -1749,7 +1269,7 @@ ] }, { - "description": "collection.listIndexNames retries at most maxAttempts=5 times", + "description": "collection.listIndexNames retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1785,36 +1305,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "listIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "listIndexes" - } - }, { "commandStartedEvent": { "commandName": "listIndexes" @@ -1850,7 +1340,7 @@ ] }, { - "description": "collection.createChangeStream retries at most maxAttempts=5 times", + "description": "collection.createChangeStream retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1889,36 +1379,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" @@ -1954,7 +1414,7 @@ ] }, { - "description": "collection.insertOne retries at most maxAttempts=5 times", + "description": "collection.insertOne retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -1996,36 +1456,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, { "commandStartedEvent": { "commandName": "insert" @@ -2061,7 +1491,7 @@ ] }, { - "description": "collection.insertMany retries at most maxAttempts=5 times", + "description": "collection.insertMany retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2105,36 +1535,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, { "commandStartedEvent": { "commandName": "insert" @@ -2170,7 +1570,7 @@ ] }, { - "description": "collection.deleteOne retries at most maxAttempts=5 times", + "description": "collection.deleteOne retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2209,36 +1609,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, { "commandStartedEvent": { "commandName": "delete" @@ -2274,7 +1644,7 @@ ] }, { - "description": "collection.deleteMany retries at most maxAttempts=5 times", + "description": "collection.deleteMany retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2313,36 +1683,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, - { - "commandStartedEvent": { - "commandName": "delete" - } - }, - { - "commandFailedEvent": { - "commandName": "delete" - } - }, { "commandStartedEvent": { "commandName": "delete" @@ -2378,7 +1718,7 @@ ] }, { - "description": "collection.replaceOne retries at most maxAttempts=5 times", + "description": "collection.replaceOne retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2420,36 +1760,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, { "commandStartedEvent": { "commandName": "update" @@ -2485,7 +1795,7 @@ ] }, { - "description": "collection.updateOne retries at most maxAttempts=5 times", + "description": "collection.updateOne retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2529,36 +1839,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, { "commandStartedEvent": { "commandName": "update" @@ -2594,7 +1874,7 @@ ] }, { - "description": "collection.updateMany retries at most maxAttempts=5 times", + "description": "collection.updateMany retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2622,52 +1902,22 @@ "object": "collection", "arguments": { "filter": {}, - "update": { - "$set": { - "x": 22 - } - } - }, - "expectError": { - "isError": true, - "isClientError": false - } - } - ], - "expectEvents": [ - { - "client": "client", - "events": [ - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" - } - }, - { - "commandStartedEvent": { - "commandName": "update" - } - }, - { - "commandFailedEvent": { - "commandName": "update" + "update": { + "$set": { + "x": 22 } - }, + } + }, + "expectError": { + "isError": true, + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ { "commandStartedEvent": { "commandName": "update" @@ -2703,7 +1953,7 @@ ] }, { - "description": "collection.findOneAndDelete retries at most maxAttempts=5 times", + "description": "collection.findOneAndDelete retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2742,36 +1992,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, { "commandStartedEvent": { "commandName": "findAndModify" @@ -2807,7 +2027,7 @@ ] }, { - "description": "collection.findOneAndReplace retries at most maxAttempts=5 times", + "description": "collection.findOneAndReplace retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2849,36 +2069,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, { "commandStartedEvent": { "commandName": "findAndModify" @@ -2914,7 +2104,7 @@ ] }, { - "description": "collection.findOneAndUpdate retries at most maxAttempts=5 times", + "description": "collection.findOneAndUpdate retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -2958,36 +2148,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandStartedEvent": { - "commandName": "findAndModify" - } - }, - { - "commandFailedEvent": { - "commandName": "findAndModify" - } - }, { "commandStartedEvent": { "commandName": "findAndModify" @@ -3023,7 +2183,7 @@ ] }, { - "description": "collection.bulkWrite retries at most maxAttempts=5 times", + "description": "collection.bulkWrite retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -3071,36 +2231,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandFailedEvent": { - "commandName": "insert" - } - }, { "commandStartedEvent": { "commandName": "insert" @@ -3136,7 +2266,7 @@ ] }, { - "description": "collection.createIndex retries at most maxAttempts=5 times", + "description": "collection.createIndex retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -3178,36 +2308,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "createIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "createIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "createIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "createIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "createIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "createIndexes" - } - }, { "commandStartedEvent": { "commandName": "createIndexes" @@ -3243,7 +2343,7 @@ ] }, { - "description": "collection.dropIndex retries at most maxAttempts=5 times", + "description": "collection.dropIndex retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -3282,36 +2382,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, { "commandStartedEvent": { "commandName": "dropIndexes" @@ -3347,7 +2417,7 @@ ] }, { - "description": "collection.dropIndexes retries at most maxAttempts=5 times", + "description": "collection.dropIndexes retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -3383,36 +2453,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandStartedEvent": { - "commandName": "dropIndexes" - } - }, - { - "commandFailedEvent": { - "commandName": "dropIndexes" - } - }, { "commandStartedEvent": { "commandName": "dropIndexes" @@ -3448,7 +2488,7 @@ ] }, { - "description": "collection.aggregate write retries at most maxAttempts=5 times", + "description": "collection.aggregate write retries at most maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -3491,36 +2531,6 @@ { "client": "client", "events": [ - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, - { - "commandStartedEvent": { - "commandName": "aggregate" - } - }, - { - "commandFailedEvent": { - "commandName": "aggregate" - } - }, { "commandStartedEvent": { "commandName": "aggregate" diff --git a/test/client-backpressure/getMore-retried.json b/test/client-backpressure/getMore-retried.json index 5fd76be679..d7607d694b 100644 --- a/test/client-backpressure/getMore-retried.json +++ b/test/client-backpressure/getMore-retried.json @@ -68,7 +68,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -145,16 +145,6 @@ "commandName": "getMore" } }, - { - "commandFailedEvent": { - "commandName": "getMore" - } - }, - { - "commandStartedEvent": { - "commandName": "getMore" - } - }, { "commandSucceededEvent": { "commandName": "getMore" @@ -165,7 +155,7 @@ ] }, { - "description": "getMores are retried maxAttempts=5 times", + "description": "getMores are retried maxAttempts=2 times", "operations": [ { "name": "failPoint", @@ -245,36 +235,6 @@ "commandName": "getMore" } }, - { - "commandStartedEvent": { - "commandName": "getMore" - } - }, - { - "commandFailedEvent": { - "commandName": "getMore" - } - }, - { - "commandStartedEvent": { - "commandName": "getMore" - } - }, - { - "commandFailedEvent": { - "commandName": "getMore" - } - }, - { - "commandStartedEvent": { - "commandName": "getMore" - } - }, - { - "commandFailedEvent": { - "commandName": "getMore" - } - }, { "commandStartedEvent": { "commandName": "killCursors" diff --git a/test/test_client.py b/test/test_client.py index 9cfa36b2cd..47d7f86515 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -645,20 +645,37 @@ def test_detected_environment_warning(self, mock_get_hosts): with self.assertWarns(UserWarning): self.simple_client(multi_host) - def test_adaptive_retries(self): - # Assert that adaptive retries are disabled by default. + def test_max_adaptive_retries(self): + # Assert that max adaptive retries default to 2. c = self.simple_client(connect=False) - self.assertFalse(c.options.adaptive_retries) + self.assertEqual(c.options.max_adaptive_retries, 2) - # Assert that adaptive retries can be enabled through connection or client options. - c = self.simple_client(connect=False, adaptive_retries=True) - self.assertTrue(c.options.adaptive_retries) + # Assert that max adaptive retries can be configured through connection or client options. + c = self.simple_client(connect=False, max_adaptive_retries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) - c = self.simple_client(connect=False, adaptiveRetries=True) - self.assertTrue(c.options.adaptive_retries) + c = self.simple_client(connect=False, maxAdaptiveRetries=10) + self.assertEqual(c.options.max_adaptive_retries, 10) - c = self.simple_client(host="mongodb://localhost/?adaptiveretries=true", connect=False) - self.assertTrue(c.options.adaptive_retries) + c = self.simple_client(host="mongodb://localhost/?maxAdaptiveRetries=10", connect=False) + self.assertEqual(c.options.max_adaptive_retries, 10) + + def test_enable_overload_retargeting(self): + # Assert that overload retargeting defaults to false. + c = self.simple_client(connect=False) + self.assertFalse(c.options.enable_overload_retargeting) + + # Assert that overload retargeting can be enabled through connection or client options. + c = self.simple_client(connect=False, enable_overload_retargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client(connect=False, enableOverloadRetargeting=True) + self.assertTrue(c.options.enable_overload_retargeting) + + c = self.simple_client( + host="mongodb://localhost/?enableOverloadRetargeting=true", connect=False + ) + self.assertTrue(c.options.enable_overload_retargeting) class TestClient(IntegrationTest): diff --git a/test/test_client_backpressure.py b/test/test_client_backpressure.py index b82846d35d..4a12edcdec 100644 --- a/test/test_client_backpressure.py +++ b/test/test_client_backpressure.py @@ -21,21 +21,19 @@ from time import perf_counter from unittest.mock import patch +from pymongo.common import MAX_ADAPTIVE_RETRIES + sys.path[0:0] = [""] from test import ( IntegrationTest, - PyMongoTestCase, client_context, unittest, ) from test.unified_format import generate_test_classes from test.utils_shared import EventListener, OvertCommandListener -import pymongo from pymongo.errors import OperationFailure, PyMongoError -from pymongo.synchronous import helpers -from pymongo.synchronous.helpers import _MAX_RETRIES, _RetryPolicy, _TokenBucket _IS_SYNC = True @@ -60,13 +58,13 @@ def test_retry_overload_error_command(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} with self.fail_point(fail_many): self.db.command("find", "t") - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: self.db.command("find", "t") @@ -80,13 +78,13 @@ def test_retry_overload_error_find(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} with self.fail_point(fail_many): self.db.t.find_one() - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: self.db.t.find_one() @@ -98,13 +96,13 @@ def test_retry_overload_error_find(self): def test_retry_overload_error_insert_one(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} with self.fail_point(fail_many): self.db.t.insert_one({"x": 1}) - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: self.db.t.insert_one({"x": 1}) @@ -120,13 +118,13 @@ def test_retry_overload_error_update_many(self): # Ensure command is retried on overload error. fail_many = mock_overload_error.copy() - fail_many["mode"] = {"times": _MAX_RETRIES} + fail_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES} with self.fail_point(fail_many): self.db.t.update_many({}, {"$set": {"x": 2}}) - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = mock_overload_error.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} with self.fail_point(fail_too_many): with self.assertRaises(PyMongoError) as error: self.db.t.update_many({}, {"$set": {"x": 2}}) @@ -142,7 +140,7 @@ def test_retry_overload_error_getMore(self): # Ensure command is retried on overload error. fail_many = { "configureFailPoint": "failCommand", - "mode": {"times": _MAX_RETRIES}, + "mode": {"times": MAX_ADAPTIVE_RETRIES}, "data": { "failCommands": ["getMore"], "errorCode": 462, # IngressRequestRateLimitExceeded @@ -154,9 +152,9 @@ def test_retry_overload_error_getMore(self): with self.fail_point(fail_many): cursor.to_list() - # Ensure command stops retrying after _MAX_RETRIES. + # Ensure command stops retrying after MAX_ADAPTIVE_RETRIES. fail_too_many = fail_many.copy() - fail_too_many["mode"] = {"times": _MAX_RETRIES + 1} + fail_too_many["mode"] = {"times": MAX_ADAPTIVE_RETRIES + 1} cursor = coll.find(batch_size=2) cursor.next() with self.fail_point(fail_too_many): @@ -167,50 +165,6 @@ def test_retry_overload_error_getMore(self): self.assertIn("SystemOverloadedError", str(error.exception)) -class TestRetryPolicy(PyMongoTestCase): - def test_retry_policy(self): - capacity = 10 - retry_policy = _RetryPolicy(_TokenBucket(capacity=capacity), adaptive_retry=True) - self.assertEqual(retry_policy.attempts, helpers._MAX_RETRIES) - self.assertEqual(retry_policy.backoff_initial, helpers._BACKOFF_INITIAL) - self.assertEqual(retry_policy.backoff_max, helpers._BACKOFF_MAX) - for i in range(1, helpers._MAX_RETRIES + 1): - self.assertTrue(retry_policy.should_retry(i, 0)) - self.assertFalse(retry_policy.should_retry(helpers._MAX_RETRIES + 1, 0)) - for i in range(capacity - helpers._MAX_RETRIES): - self.assertTrue(retry_policy.should_retry(1, 0)) - # No tokens left, should not retry. - self.assertFalse(retry_policy.should_retry(1, 0)) - self.assertEqual(retry_policy.token_bucket.tokens, 0) - - # record_success should generate tokens. - for _ in range(int(2 / helpers.DEFAULT_RETRY_TOKEN_RETURN)): - retry_policy.record_success(retry=False) - self.assertAlmostEqual(retry_policy.token_bucket.tokens, 2) - for i in range(2): - self.assertTrue(retry_policy.should_retry(1, 0)) - self.assertFalse(retry_policy.should_retry(1, 0)) - - # Recording a successful retry should return 1 additional token. - retry_policy.record_success(retry=True) - self.assertAlmostEqual( - retry_policy.token_bucket.tokens, 1 + helpers.DEFAULT_RETRY_TOKEN_RETURN - ) - self.assertTrue(retry_policy.should_retry(1, 0)) - self.assertFalse(retry_policy.should_retry(1, 0)) - self.assertAlmostEqual(retry_policy.token_bucket.tokens, helpers.DEFAULT_RETRY_TOKEN_RETURN) - - def test_retry_policy_csot(self): - retry_policy = _RetryPolicy(_TokenBucket()) - self.assertTrue(retry_policy.should_retry(1, 0.5)) - with pymongo.timeout(0.5): - self.assertTrue(retry_policy.should_retry(1, 0)) - self.assertTrue(retry_policy.should_retry(1, 0.1)) - # Would exceed the timeout, should not retry. - self.assertFalse(retry_policy.should_retry(1, 1.0)) - self.assertTrue(retry_policy.should_retry(1, 1.0)) - - # Prose tests. class TestClientBackpressure(IntegrationTest): listener: EventListener @@ -270,14 +224,14 @@ def test_01_operation_retry_uses_exponential_backoff(self, random_func): collection.insert_one({"a": 1}) end1 = perf_counter() - # f. Compare the two time between the two runs. - # The sum of 5 backoffs is 3.1 seconds. There is a 1-second window to account for potential variance between the two + # f. Compare the times between the two runs. + # The sum of 2 backoffs is 0.3 seconds. There is a 0.3-second window to account for potential variance between the two # runs. - self.assertTrue(abs((end1 - start1) - (end0 - start0 + 3.1)) < 1) + self.assertTrue(abs((end1 - start1) - (end0 - start0 + 0.3)) < 0.3) @client_context.require_failCommand_appName def test_03_overload_retries_limited(self): - # Drivers should test that without adaptive retries enabled, overload errors are retried a maximum of five times. + # Drivers should test that overload errors are retried a maximum of two times. # 1. Let `client` be a `MongoClient`. client = self.client @@ -300,28 +254,27 @@ def test_03_overload_retries_limited(self): with self.assertRaises(PyMongoError) as error: coll.find_one({}) - # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverLoadedError` error labels. + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. self.assertIn("RetryableError", str(error.exception)) self.assertIn("SystemOverloadedError", str(error.exception)) - # 6. Assert that the total number of started commands is MAX_RETRIES + 1. - self.assertEqual(len(self.listener.started_events), _MAX_RETRIES + 1) + # 6. Assert that the total number of started commands is MAX_ADAPTIVE_RETRIES + 1. + self.assertEqual(len(self.listener.started_events), MAX_ADAPTIVE_RETRIES + 1) @client_context.require_failCommand_appName - def test_04_adaptive_retries_limited_by_tokens(self): - # Drivers should test that when enabled, adaptive retries are limited by the number of tokens in the bucket. - - # 1. Let `client` be a `MongoClient` with adaptiveRetries=True. - client = self.rs_or_single_client(adaptive_retries=True, event_listeners=[self.listener]) - # 2. Set `client`'s retry token bucket to have 2 tokens. - client._retry_policy.token_bucket.tokens = 2 - # 3. Let `coll` be a collection. + def test_04_overload_retries_limited_configured(self): + # Drivers should test that overload errors are retried a maximum of maxAdaptiveRetries times. + max_retries = 1 + + # 1. Let `client` be a `MongoClient` with `maxAdaptiveRetries=1` and command event monitoring enabled. + client = self.single_client(maxAdaptiveRetries=max_retries, event_listeners=[self.listener]) + # 2. Let `coll` be a collection. coll = client.pymongo_test.coll - # 4. Configure the following failpoint: + # 3. Configure the following failpoint: failpoint = { "configureFailPoint": "failCommand", - "mode": {"times": 3}, + "mode": "alwaysOn", "data": { "failCommands": ["find"], "errorCode": 462, # IngressRequestRateLimitExceeded @@ -329,17 +282,17 @@ def test_04_adaptive_retries_limited_by_tokens(self): }, } - # 5. Perform a find operation with `coll` that fails. + # 4. Perform a find operation with `coll` that fails. with self.fail_point(failpoint): with self.assertRaises(PyMongoError) as error: coll.find_one({}) - # 6. Assert that the raised error contains both the `RetryableError` and `SystemOverLoadedError` error labels. + # 5. Assert that the raised error contains both the `RetryableError` and `SystemOverloadedError` error labels. self.assertIn("RetryableError", str(error.exception)) self.assertIn("SystemOverloadedError", str(error.exception)) - # 7. Assert that the total number of started commands is 3: one for the initial attempt and two for the retries. - self.assertEqual(len(self.listener.started_events), 3) + # 6. Assert that the total number of started commands is max_retries + 1. + self.assertEqual(len(self.listener.started_events), max_retries + 1) # Location of JSON test specifications. diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index 18cd669f1c..9e6aac821c 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -263,14 +263,17 @@ def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @client_context.require_secondaries_count(1) @client_context.require_failCommand_fail_point @client_context.require_version_min(4, 4, 0) - def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available( + def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available_and_overload_retargeting_is_enabled( self ): listener = OvertCommandListener() - # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, `enableOverloadRetargeting=True`, and command event monitoring enabled. client = self.rs_or_single_client( - event_listeners=[listener], retryReads=True, readPreference="primaryPreferred" + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + enableOverloadRetargeting=True, ) # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. @@ -337,6 +340,47 @@ def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_ # 6. Assert that both events occurred the same server. assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + @client_context.require_replica_set + @client_context.require_secondaries_count(1) + @client_context.require_failCommand_fail_point + @client_context.require_version_min(4, 4, 0) + def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same_replicaset_server_when_one_is_available_and_overload_retargeting_is_disabled( + self + ): + listener = OvertCommandListener() + + # 1. Create a client `client` with `retryReads=true`, `readPreference=primaryPreferred`, and command event monitoring enabled. + client = self.rs_or_single_client( + event_listeners=[listener], + retryReads=True, + readPreference="primaryPreferred", + ) + + # 2. Configure a fail point with the RetryableError and SystemOverloadedError error labels. + command_args = { + "configureFailPoint": "failCommand", + "mode": {"times": 1}, + "data": { + "failCommands": ["find"], + "errorLabels": ["RetryableError", "SystemOverloadedError"], + "errorCode": 6, + }, + } + set_fail_point(client, command_args) + + # 3. Reset the command event monitor to clear the fail point command from its stored events. + listener.reset() + + # 4. Execute a `find` command with `client`. + client.t.t.find_one({}) + + # 5. Assert that one failed command event and one successful command event occurred. + self.assertEqual(len(listener.failed_events), 1) + self.assertEqual(len(listener.succeeded_events), 1) + + # 6. Assert that both events occurred on the same server. + assert listener.failed_events[0].connection_id == listener.succeeded_events[0].connection_id + if __name__ == "__main__": unittest.main() diff --git a/test/transactions/unified/backpressure-retryable-abort.json b/test/transactions/unified/backpressure-retryable-abort.json index 53fc9c6f09..3a2a3b4368 100644 --- a/test/transactions/unified/backpressure-retryable-abort.json +++ b/test/transactions/unified/backpressure-retryable-abort.json @@ -213,7 +213,7 @@ ] }, { - "description": "abortTransaction is retried maxAttempts=5 times if backpressure labels are added", + "description": "abortTransaction is retried maxAttempts=2 times if backpressure labels are added", "operations": [ { "object": "testRunner", @@ -322,21 +322,6 @@ "commandName": "abortTransaction" } }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "abortTransaction" - } - }, { "commandStartedEvent": { "commandName": "abortTransaction" diff --git a/test/transactions/unified/backpressure-retryable-commit.json b/test/transactions/unified/backpressure-retryable-commit.json index ae873561a9..844ed25ab4 100644 --- a/test/transactions/unified/backpressure-retryable-commit.json +++ b/test/transactions/unified/backpressure-retryable-commit.json @@ -222,7 +222,7 @@ ] }, { - "description": "commitTransaction is retried maxAttempts=5 times if backpressure labels are added", + "description": "commitTransaction is retried maxAttempts=2 times if backpressure labels are added", "runOnRequirements": [ { "serverless": "forbid" @@ -339,21 +339,6 @@ "commandName": "commitTransaction" } }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, - { - "commandStartedEvent": { - "commandName": "commitTransaction" - } - }, { "commandStartedEvent": { "commandName": "commitTransaction" diff --git a/test/transactions/unified/backpressure-retryable-reads.json b/test/transactions/unified/backpressure-retryable-reads.json index 731762830e..a859ec4bda 100644 --- a/test/transactions/unified/backpressure-retryable-reads.json +++ b/test/transactions/unified/backpressure-retryable-reads.json @@ -216,7 +216,7 @@ ] }, { - "description": "reads are retried maxAttempts=5 times if backpressure labels are added", + "description": "reads are retried maxAttempts=2 times if backpressure labels are added", "operations": [ { "object": "session0", @@ -300,21 +300,6 @@ "commandName": "find" } }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, - { - "commandStartedEvent": { - "commandName": "find" - } - }, { "commandStartedEvent": { "commandName": "abortTransaction" diff --git a/test/transactions/unified/backpressure-retryable-writes.json b/test/transactions/unified/backpressure-retryable-writes.json index eea0e6b5da..6cbf450e5f 100644 --- a/test/transactions/unified/backpressure-retryable-writes.json +++ b/test/transactions/unified/backpressure-retryable-writes.json @@ -244,7 +244,7 @@ ] }, { - "description": "writes are retried maxAttempts=5 times if backpressure labels are added", + "description": "writes are retried maxAttempts=2 times if backpressure labels are added", "operations": [ { "object": "session0", @@ -330,21 +330,6 @@ "commandName": "insert" } }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, - { - "commandStartedEvent": { - "commandName": "insert" - } - }, { "commandStartedEvent": { "commandName": "abortTransaction" diff --git a/test/uri_options/client-backpressure-options.json b/test/uri_options/client-backpressure-options.json index 3fcf2c86b0..3e501d1f4c 100644 --- a/test/uri_options/client-backpressure-options.json +++ b/test/uri_options/client-backpressure-options.json @@ -1,30 +1,61 @@ { "tests": [ { - "description": "adaptiveRetries=true is parsed correctly", - "uri": "mongodb://example.com/?adaptiveRetries=true", + "description": "maxAdaptiveRetries is parsed correctly", + "uri": "mongodb://example.com/?maxAdaptiveRetries=3", "valid": true, "warning": false, "hosts": null, "auth": null, "options": { - "adaptiveRetries": true + "maxAdaptiveRetries": 3 } }, { - "description": "adaptiveRetries=false is parsed correctly", - "uri": "mongodb://example.com/?adaptiveRetries=false", + "description": "maxAdaptiveRetries=0 is parsed correctly", + "uri": "mongodb://example.com/?maxAdaptiveRetries=0", "valid": true, "warning": false, "hosts": null, "auth": null, "options": { - "adaptiveRetries": false + "maxAdaptiveRetries": 0 } }, { - "description": "adaptiveRetries with invalid value causes a warning", - "uri": "mongodb://example.com/?adaptiveRetries=invalid", + "description": "maxAdaptiveRetries with invalid value causes a warning", + "uri": "mongodb://example.com/?maxAdaptiveRetries=-5", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "enableOverloadRetargeting is parsed correctly", + "uri": "mongodb://example.com/?enableOverloadRetargeting=true", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "enableOverloadRetargeting": true + } + }, + { + "description": "enableOverloadRetargeting=false is parsed correctly", + "uri": "mongodb://example.com/?enableOverloadRetargeting=false", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "enableOverloadRetargeting": false + } + }, + { + "description": "enableOverloadRetargeting with invalid value causes a warning", + "uri": "mongodb://example.com/?enableOverloadRetargeting=invalid", "valid": true, "warning": true, "hosts": null,