Skip to content

Commit f09b2e9

Browse files
Switch retry (#13264)
* new retry policy, now going to remove the other ones to verify everything works * working async policy now too * pylint fixes * changed from exponential to new StorageRetryPolicy * attempts to fix 415 error * syntax error * linting fixes * fixing up izzys comments * renamed to (Async)TablesRetryPolicy
1 parent 1dad954 commit f09b2e9

File tree

4 files changed

+119
-43
lines changed

4 files changed

+119
-43
lines changed

sdk/tables/azure-data-tables/azure/data/tables/_base_client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
StorageRequestHook,
5151
StorageResponseHook,
5252
StorageLoggingPolicy,
53-
StorageHosts, ExponentialRetry,
53+
StorageHosts,
54+
TablesRetryPolicy,
5455
)
5556
from ._error import _process_table_error
5657
from ._models import PartialBatchErrorException
@@ -391,7 +392,7 @@ def create_configuration(**kwargs):
391392
config.headers_policy = StorageHeadersPolicy(**kwargs)
392393
config.user_agent_policy = UserAgentPolicy(sdk_moniker=SDK_MONIKER, **kwargs)
393394
# sdk_moniker="storage-{}/{}".format(kwargs.pop('storage_sdk'), VERSION), **kwargs)
394-
config.retry_policy = kwargs.get("retry_policy") or ExponentialRetry(**kwargs)
395+
config.retry_policy = kwargs.get("retry_policy") or TablesRetryPolicy(**kwargs)
395396
config.logging_policy = StorageLoggingPolicy(**kwargs)
396397
config.proxy_policy = ProxyPolicy(**kwargs)
397398

sdk/tables/azure-data-tables/azure/data/tables/_policies.py

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
SansIOHTTPPolicy,
3737
NetworkTraceLoggingPolicy,
3838
HTTPPolicy,
39-
RequestHistory
39+
RequestHistory,
40+
RetryPolicy
4041
)
4142
from azure.core.exceptions import AzureError, ServiceRequestError, ServiceResponseError
4243

@@ -353,18 +354,61 @@ def on_response(self, request, response):
353354
)
354355

355356

356-
class StorageRetryPolicy(HTTPPolicy):
357+
class TablesRetryPolicy(RetryPolicy):
357358
"""
358-
The base class for Exponential and Linear retries containing shared code.
359+
A base class for retry policies for the Table Client and Table Service Client
359360
"""
361+
def __init__(
362+
self,
363+
initial_backoff=15, # type: int
364+
increment_base=3, # type: int
365+
retry_total=10, # type: int
366+
retry_to_secondary=False, # type: bool
367+
random_jitter_range=3, # type: int
368+
**kwargs # type: Any
369+
):
370+
"""
371+
Build a TablesRetryPolicy object.
360372
361-
def __init__(self, **kwargs):
362-
self.total_retries = kwargs.pop('retry_total', 10)
373+
:param int initial_backoff:
374+
The initial backoff interval, in seconds, for the first retry.
375+
:param int increment_base:
376+
The base, in seconds, to increment the initial_backoff by after the
377+
first retry.
378+
:param int retry_total: total number of retries
379+
:param bool retry_to_secondary:
380+
Whether the request should be retried to secondary, if able. This should
381+
only be enabled of RA-GRS accounts are used and potentially stale data
382+
can be handled.
383+
:param int random_jitter_range:
384+
A number in seconds which indicates a range to jitter/randomize for the back-off interval.
385+
For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3.
386+
"""
387+
self.initial_backoff = initial_backoff
388+
self.increment_base = increment_base
389+
self.random_jitter_range = random_jitter_range
390+
self.total_retries = retry_total
363391
self.connect_retries = kwargs.pop('retry_connect', 3)
364392
self.read_retries = kwargs.pop('retry_read', 3)
365393
self.status_retries = kwargs.pop('retry_status', 3)
366-
self.retry_to_secondary = kwargs.pop('retry_to_secondary', False)
367-
super(StorageRetryPolicy, self).__init__()
394+
self.retry_to_secondary = retry_to_secondary
395+
super(TablesRetryPolicy, self).__init__(**kwargs)
396+
397+
def get_backoff_time(self, settings):
398+
"""
399+
Calculates how long to sleep before retrying.
400+
:param dict settings:
401+
:keyword callable cls: A custom type or function that will be passed the direct response
402+
:return:
403+
An integer indicating how long to wait before retrying the request,
404+
or None to indicate no retry should be performed.
405+
:rtype: int or None
406+
"""
407+
random_generator = random.Random()
408+
backoff = self.initial_backoff + (0 if settings['count'] == 0 else pow(self.increment_base, settings['count']))
409+
random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0
410+
random_range_end = backoff + self.random_jitter_range
411+
return random_generator.uniform(random_range_start, random_range_end)
368412

369413
def _set_next_host_location(self, settings, request): # pylint: disable=no-self-use
370414
"""
@@ -384,7 +428,7 @@ def _set_next_host_location(self, settings, request): # pylint: disable=no-self
384428
updated = url._replace(netloc=settings['hosts'].get(settings['mode']))
385429
request.url = updated.geturl()
386430

387-
def configure_retries(self, request): # pylint: disable=no-self-use
431+
def configure_retries(self, request): # pylint: disable=no-self-use, arguments-differ
388432
# type: (...)-> dict
389433
"""
390434
:param Any request:
@@ -414,17 +458,8 @@ def configure_retries(self, request): # pylint: disable=no-self-use
414458
'history': []
415459
}
416460

417-
def get_backoff_time(self, settings, **kwargs): # pylint: disable=unused-argument,no-self-use
418-
""" Formula for computing the current backoff.
419-
Should be calculated by child class.
420-
:param Any settings:
421-
:keyword callable cls: A custom type or function that will be passed the direct response
422-
:rtype: float
423-
"""
424-
return 0
425-
426-
def sleep(self, settings, transport):
427-
# type: (...)->None
461+
def sleep(self, settings, transport): # pylint: disable=arguments-differ
462+
# type: (...) -> None
428463
"""
429464
:param Any settings:
430465
:param Any transport:
@@ -435,7 +470,7 @@ def sleep(self, settings, transport):
435470
return
436471
transport.sleep(backoff)
437472

438-
def increment(self, settings, request, response=None, error=None, **kwargs): # pylint:disable=W0613
473+
def increment(self, settings, request, response=None, error=None, **kwargs): # pylint:disable=unused-argument, arguments-differ
439474
# type: (...)->None
440475
"""Increment the retry counters.
441476
@@ -531,7 +566,7 @@ def send(self, request):
531566
return response
532567

533568

534-
class ExponentialRetry(StorageRetryPolicy):
569+
class ExponentialRetry(TablesRetryPolicy):
535570
"""Exponential retry."""
536571

537572
def __init__(self, initial_backoff=15, increment_base=3, retry_total=3,
@@ -565,10 +600,9 @@ def __init__(self, initial_backoff=15, increment_base=3, retry_total=3,
565600
super(ExponentialRetry, self).__init__(
566601
retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)
567602

568-
def get_backoff_time(self, settings, **kwargs):
603+
def get_backoff_time(self, settings):
569604
"""
570605
Calculates how long to sleep before retrying.
571-
:param **kwargs:
572606
:param dict settings:
573607
:keyword callable cls: A custom type or function that will be passed the direct response
574608
:return:
@@ -583,7 +617,7 @@ def get_backoff_time(self, settings, **kwargs):
583617
return random_generator.uniform(random_range_start, random_range_end)
584618

585619

586-
class LinearRetry(StorageRetryPolicy):
620+
class LinearRetry(TablesRetryPolicy):
587621
"""Linear retry."""
588622

589623
def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_jitter_range=3, **kwargs):
@@ -608,7 +642,7 @@ def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_j
608642
super(LinearRetry, self).__init__(
609643
retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)
610644

611-
def get_backoff_time(self, settings, **kwargs):
645+
def get_backoff_time(self, settings):
612646
"""
613647
Calculates how long to sleep before retrying.
614648

sdk/tables/azure-data-tables/azure/data/tables/aio/_policies_async.py

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
import logging
1010
from typing import Any, TYPE_CHECKING
1111

12-
from azure.core.pipeline.policies import AsyncHTTPPolicy
12+
from azure.core.pipeline.policies import AsyncHTTPPolicy, AsyncRetryPolicy
1313
from azure.core.exceptions import AzureError
1414

15-
from .._policies import is_retry, StorageRetryPolicy
15+
from .._policies import is_retry, TablesRetryPolicy
1616

1717
if TYPE_CHECKING:
1818
from azure.core.pipeline import PipelineRequest, PipelineResponse
@@ -78,12 +78,56 @@ async def send(self, request):
7878
request.context['response_callback'] = response_callback
7979
return response
8080

81-
class AsyncStorageRetryPolicy(StorageRetryPolicy):
82-
"""
83-
The base class for Exponential and Linear retries containing shared code.
84-
"""
8581

86-
async def sleep(self, settings, transport): # pylint: disable =W0236
82+
class AsyncTablesRetryPolicy(AsyncRetryPolicy, TablesRetryPolicy):
83+
"""Exponential retry."""
84+
85+
def __init__(self, initial_backoff=15, increment_base=3, retry_total=3,
86+
retry_to_secondary=False, random_jitter_range=3, **kwargs):
87+
'''
88+
Constructs an Exponential retry object. The initial_backoff is used for
89+
the first retry. Subsequent retries are retried after initial_backoff +
90+
increment_power^retry_count seconds. For example, by default the first retry
91+
occurs after 15 seconds, the second after (15+3^1) = 18 seconds, and the
92+
third after (15+3^2) = 24 seconds.
93+
94+
:param int initial_backoff:
95+
The initial backoff interval, in seconds, for the first retry.
96+
:param int increment_base:
97+
The base, in seconds, to increment the initial_backoff by after the
98+
first retry.
99+
:param int max_attempts:
100+
The maximum number of retry attempts.
101+
:param bool retry_to_secondary:
102+
Whether the request should be retried to secondary, if able. This should
103+
only be enabled of RA-GRS accounts are used and potentially stale data
104+
can be handled.
105+
:param int random_jitter_range:
106+
A number in seconds which indicates a range to jitter/randomize for the back-off interval.
107+
For example, a random_jitter_range of 3 results in the back-off interval x to vary between x+3 and x-3.
108+
'''
109+
self.initial_backoff = initial_backoff
110+
self.increment_base = increment_base
111+
self.random_jitter_range = random_jitter_range
112+
super(AsyncTablesRetryPolicy, self).__init__(
113+
retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)
114+
115+
def get_backoff_time(self, settings):
116+
"""
117+
Calculates how long to sleep before retrying.
118+
119+
:return:
120+
An integer indicating how long to wait before retrying the request,
121+
or None to indicate no retry should be performed.
122+
:rtype: int or None
123+
"""
124+
random_generator = random.Random()
125+
backoff = self.initial_backoff + (0 if settings['count'] == 0 else pow(self.increment_base, settings['count']))
126+
random_range_start = backoff - self.random_jitter_range if backoff > self.random_jitter_range else 0
127+
random_range_end = backoff + self.random_jitter_range
128+
return random_generator.uniform(random_range_start, random_range_end)
129+
130+
async def sleep(self, settings, transport): # pylint: disable=W0236, arguments-differ
87131
backoff = self.get_backoff_time(settings)
88132
if not backoff or backoff < 0:
89133
return
@@ -99,7 +143,6 @@ async def send(self, request): # pylint: disable =W0236
99143
if is_retry(response, retry_settings['mode']):
100144
retries_remaining = self.increment(
101145
retry_settings,
102-
request=request.http_request,
103146
response=response.http_response)
104147
if retries_remaining:
105148
await retry_hook(
@@ -111,8 +154,7 @@ async def send(self, request): # pylint: disable =W0236
111154
continue
112155
break
113156
except AzureError as err:
114-
retries_remaining = self.increment(
115-
retry_settings, request=request.http_request, error=err)
157+
retries_remaining = self.increment(retry_settings, error=err)
116158
if retries_remaining:
117159
await retry_hook(
118160
retry_settings,
@@ -128,7 +170,7 @@ async def send(self, request): # pylint: disable =W0236
128170
return response
129171

130172

131-
class ExponentialRetry(AsyncStorageRetryPolicy):
173+
class ExponentialRetry(AsyncTablesRetryPolicy):
132174
"""Exponential retry."""
133175

134176
def __init__(self, initial_backoff=15, increment_base=3, retry_total=3,
@@ -161,11 +203,10 @@ def __init__(self, initial_backoff=15, increment_base=3, retry_total=3,
161203
super(ExponentialRetry, self).__init__(
162204
retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)
163205

164-
def get_backoff_time(self, settings, **kwargs):
206+
def get_backoff_time(self, settings):
165207
"""
166208
Calculates how long to sleep before retrying.
167209
168-
:param **kwargs:
169210
:return:
170211
An integer indicating how long to wait before retrying the request,
171212
or None to indicate no retry should be performed.
@@ -178,7 +219,7 @@ def get_backoff_time(self, settings, **kwargs):
178219
return random_generator.uniform(random_range_start, random_range_end)
179220

180221

181-
class LinearRetry(AsyncStorageRetryPolicy):
222+
class LinearRetry(AsyncTablesRetryPolicy):
182223
"""Linear retry."""
183224

184225
def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_jitter_range=3, **kwargs):
@@ -202,7 +243,7 @@ def __init__(self, backoff=15, retry_total=3, retry_to_secondary=False, random_j
202243
super(LinearRetry, self).__init__(
203244
retry_total=retry_total, retry_to_secondary=retry_to_secondary, **kwargs)
204245

205-
def get_backoff_time(self, settings, **kwargs):
246+
def get_backoff_time(self, settings, **kwargs): # pylint: disable=unused-argument
206247
"""
207248
Calculates how long to sleep before retrying.
208249

sdk/tables/azure-data-tables/tests/test_table_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ async def test_set_table_acl_with_empty_signed_identifiers(self, resource_group,
283283

284284
@pytest.mark.skip("pending")
285285
@GlobalStorageAccountPreparer()
286-
async def test_set_table_acl_with_empty_signed_identifier(self, resource_group, location, storage_account,
286+
async def test_set_table_acl_with_none_signed_identifier(self, resource_group, location, storage_account,
287287
storage_account_key):
288288
# Arrange
289289
url = self.account_url(storage_account, "table")

0 commit comments

Comments
 (0)