Skip to content

Commit 6af5eaf

Browse files
authored
[Core] Fix ignored retry_backoff_max in RetryPolicies (#42444)
Signed-off-by: Paul Van Eck <[email protected]>
1 parent bcec119 commit 6af5eaf

File tree

4 files changed

+195
-1
lines changed

4 files changed

+195
-1
lines changed

sdk/core/azure-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Fixed an issue where the `retry_backoff_max` parameter in `RetryPolicy` and `AsyncRetryPolicy` constructors was being ignored, causing retry operations to use default maximum backoff values instead of the user-specified limits. #42444
12+
1113
### Other Changes
1214

1315
## 1.35.0 (2025-07-02)

sdk/core/azure-core/azure/core/pipeline/policies/_retry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def configure_retries(self, options: Dict[str, Any]) -> Dict[str, Any]:
118118
"read": options.pop("retry_read", self.read_retries),
119119
"status": options.pop("retry_status", self.status_retries),
120120
"backoff": options.pop("retry_backoff_factor", self.backoff_factor),
121-
"max_backoff": options.pop("retry_backoff_max", self.BACKOFF_MAX),
121+
"max_backoff": options.pop("retry_backoff_max", self.backoff_max),
122122
"methods": options.pop("retry_on_methods", self._method_whitelist),
123123
"timeout": options.pop("timeout", self.timeout),
124124
"history": [],

sdk/core/azure-core/tests/async_tests/test_retry_policy_async.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,99 @@ async def test_does_not_sleep_after_timeout(combinations, http_request):
314314
await pipeline.run(http_request("GET", "http://localhost/"))
315315

316316
assert transport.sleep.call_count == 1
317+
318+
319+
def test_configure_retries_uses_constructor_values():
320+
"""Test that configure_retries method correctly gets values from constructor."""
321+
# Test with custom constructor values
322+
retry_policy = AsyncRetryPolicy(
323+
retry_total=5,
324+
retry_connect=2,
325+
retry_read=4,
326+
retry_status=1,
327+
retry_backoff_factor=0.5,
328+
retry_backoff_max=60,
329+
timeout=300,
330+
)
331+
332+
# Call configure_retries with empty options to ensure it uses constructor values
333+
retry_settings = retry_policy.configure_retries({})
334+
335+
# Verify that configure_retries returns constructor values as defaults
336+
assert retry_settings["total"] == 5
337+
assert retry_settings["connect"] == 2
338+
assert retry_settings["read"] == 4
339+
assert retry_settings["status"] == 1
340+
assert retry_settings["backoff"] == 0.5
341+
assert retry_settings["max_backoff"] == 60
342+
assert retry_settings["timeout"] == 300
343+
assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
344+
assert retry_settings["history"] == []
345+
346+
347+
def test_configure_retries_options_override_constructor():
348+
"""Test that options passed to configure_retries override constructor values."""
349+
# Test with custom constructor values
350+
retry_policy = AsyncRetryPolicy(
351+
retry_total=5,
352+
retry_connect=2,
353+
retry_read=4,
354+
retry_status=1,
355+
retry_backoff_factor=0.5,
356+
retry_backoff_max=60,
357+
timeout=300,
358+
)
359+
360+
# Call configure_retries with options that should override constructor values
361+
options = {
362+
"retry_total": 8,
363+
"retry_connect": 6,
364+
"retry_read": 7,
365+
"retry_status": 3,
366+
"retry_backoff_factor": 1.0,
367+
"retry_backoff_max": 180,
368+
"timeout": 600,
369+
"retry_on_methods": frozenset(["GET", "POST"]),
370+
}
371+
retry_settings = retry_policy.configure_retries(options)
372+
373+
# Verify that configure_retries returns option values, not constructor values
374+
assert retry_settings["total"] == 8
375+
assert retry_settings["connect"] == 6
376+
assert retry_settings["read"] == 7
377+
assert retry_settings["status"] == 3
378+
assert retry_settings["backoff"] == 1.0
379+
assert retry_settings["max_backoff"] == 180
380+
assert retry_settings["timeout"] == 600
381+
assert retry_settings["methods"] == frozenset(["GET", "POST"])
382+
assert retry_settings["history"] == []
383+
384+
# Verify options dict was modified (values were popped)
385+
assert "retry_total" not in options
386+
assert "retry_connect" not in options
387+
assert "retry_read" not in options
388+
assert "retry_status" not in options
389+
assert "retry_backoff_factor" not in options
390+
assert "retry_backoff_max" not in options
391+
assert "timeout" not in options
392+
assert "retry_on_methods" not in options
393+
394+
395+
def test_configure_retries_default_values():
396+
"""Test that configure_retries uses default values when no constructor args or options provided."""
397+
# Test with default constructor
398+
retry_policy = AsyncRetryPolicy()
399+
400+
# Call configure_retries with empty options
401+
retry_settings = retry_policy.configure_retries({})
402+
403+
# Verify default values from RetryPolicyBase.__init__
404+
assert retry_settings["total"] == 10 # default retry_total
405+
assert retry_settings["connect"] == 3 # default retry_connect
406+
assert retry_settings["read"] == 3 # default retry_read
407+
assert retry_settings["status"] == 3 # default retry_status
408+
assert retry_settings["backoff"] == 0.8 # default retry_backoff_factor
409+
assert retry_settings["max_backoff"] == 120 # default retry_backoff_max (BACKOFF_MAX)
410+
assert retry_settings["timeout"] == 604800 # default timeout
411+
assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
412+
assert retry_settings["history"] == []

sdk/core/azure-core/tests/test_retry_policy.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,99 @@ def test_does_not_sleep_after_timeout(combinations, http_request):
314314
pipeline.run(http_request("GET", "http://localhost/"))
315315

316316
assert transport.sleep.call_count == 1
317+
318+
319+
def test_configure_retries_uses_constructor_values():
320+
"""Test that configure_retries method correctly gets values from constructor."""
321+
# Test with custom constructor values
322+
retry_policy = RetryPolicy(
323+
retry_total=5,
324+
retry_connect=2,
325+
retry_read=4,
326+
retry_status=1,
327+
retry_backoff_factor=0.5,
328+
retry_backoff_max=60,
329+
timeout=300,
330+
)
331+
332+
# Call configure_retries with empty options to ensure it uses constructor values
333+
retry_settings = retry_policy.configure_retries({})
334+
335+
# Verify that configure_retries returns constructor values as defaults
336+
assert retry_settings["total"] == 5
337+
assert retry_settings["connect"] == 2
338+
assert retry_settings["read"] == 4
339+
assert retry_settings["status"] == 1
340+
assert retry_settings["backoff"] == 0.5
341+
assert retry_settings["max_backoff"] == 60
342+
assert retry_settings["timeout"] == 300
343+
assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
344+
assert retry_settings["history"] == []
345+
346+
347+
def test_configure_retries_options_override_constructor():
348+
"""Test that options passed to configure_retries override constructor values."""
349+
# Test with custom constructor values
350+
retry_policy = RetryPolicy(
351+
retry_total=5,
352+
retry_connect=2,
353+
retry_read=4,
354+
retry_status=1,
355+
retry_backoff_factor=0.5,
356+
retry_backoff_max=60,
357+
timeout=300,
358+
)
359+
360+
# Call configure_retries with options that should override constructor values
361+
options = {
362+
"retry_total": 8,
363+
"retry_connect": 6,
364+
"retry_read": 7,
365+
"retry_status": 3,
366+
"retry_backoff_factor": 1.0,
367+
"retry_backoff_max": 180,
368+
"timeout": 600,
369+
"retry_on_methods": frozenset(["GET", "POST"]),
370+
}
371+
retry_settings = retry_policy.configure_retries(options)
372+
373+
# Verify that configure_retries returns option values, not constructor values
374+
assert retry_settings["total"] == 8
375+
assert retry_settings["connect"] == 6
376+
assert retry_settings["read"] == 7
377+
assert retry_settings["status"] == 3
378+
assert retry_settings["backoff"] == 1.0
379+
assert retry_settings["max_backoff"] == 180
380+
assert retry_settings["timeout"] == 600
381+
assert retry_settings["methods"] == frozenset(["GET", "POST"])
382+
assert retry_settings["history"] == []
383+
384+
# Verify options dict was modified (values were popped)
385+
assert "retry_total" not in options
386+
assert "retry_connect" not in options
387+
assert "retry_read" not in options
388+
assert "retry_status" not in options
389+
assert "retry_backoff_factor" not in options
390+
assert "retry_backoff_max" not in options
391+
assert "timeout" not in options
392+
assert "retry_on_methods" not in options
393+
394+
395+
def test_configure_retries_default_values():
396+
"""Test that configure_retries uses default values when no constructor args or options provided."""
397+
# Test with default constructor
398+
retry_policy = RetryPolicy()
399+
400+
# Call configure_retries with empty options
401+
retry_settings = retry_policy.configure_retries({})
402+
403+
# Verify default values from RetryPolicyBase.__init__
404+
assert retry_settings["total"] == 10 # default retry_total
405+
assert retry_settings["connect"] == 3 # default retry_connect
406+
assert retry_settings["read"] == 3 # default retry_read
407+
assert retry_settings["status"] == 3 # default retry_status
408+
assert retry_settings["backoff"] == 0.8 # default retry_backoff_factor
409+
assert retry_settings["max_backoff"] == 120 # default retry_backoff_max (BACKOFF_MAX)
410+
assert retry_settings["timeout"] == 604800 # default timeout
411+
assert retry_settings["methods"] == frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"])
412+
assert retry_settings["history"] == []

0 commit comments

Comments
 (0)