Skip to content

Commit 1f358d0

Browse files
committed
inital fixed retry commit
1 parent 9c6296d commit 1f358d0

File tree

9 files changed

+137
-4
lines changed

9 files changed

+137
-4
lines changed

sdk/servicebus/azure-servicebus/azure/servicebus/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
parse_connection_string,
2929
ServiceBusConnectionStringProperties,
3030
)
31+
from ._retry import RetryMode
3132

3233
TransportType = constants.TransportType
3334

@@ -46,4 +47,5 @@
4647
"AutoLockRenewer",
4748
"parse_connection_string",
4849
"ServiceBusConnectionStringProperties",
50+
"RetryMode",
4951
]

sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from azure.core.credentials import AccessToken, AzureSasCredential, AzureNamedKeyCredential
2323

2424
from ._common._configuration import Configuration
25+
from ._retry import RetryMode
2526
from .exceptions import (
2627
ServiceBusError,
2728
ServiceBusConnectionError,
@@ -152,6 +153,12 @@ def _generate_sas_token(uri, policy, key, expiry=None):
152153
token = utils.create_sas_token(encoded_policy, encoded_key, encoded_uri, expiry)
153154
return AccessToken(token=token, expires_on=abs_expiry)
154155

156+
def _get_backoff_time(retry_mode, backoff_factor, backoff_max, retried_times):
157+
if retry_mode == RetryMode.FIXED:
158+
backoff_value = backoff_factor
159+
else:
160+
backoff_value = backoff_factor * (2 ** retried_times)
161+
return min(backoff_max, backoff_value)
155162

156163
class ServiceBusSASTokenCredential(object):
157164
"""The shared access token credential used for authentication.
@@ -418,7 +425,12 @@ def _backoff(
418425
):
419426
# type: (int, Exception, Optional[float], str) -> None
420427
entity_name = entity_name or self._container_id
421-
backoff = self._config.retry_backoff_factor * 2 ** retried_times
428+
backoff = _get_backoff_time(
429+
self._config.retry_mode,
430+
self._config.retry_backoff_factor,
431+
self._config.retry_backoff_max,
432+
retried_times,
433+
)
422434
if backoff <= self._config.retry_backoff_max and (
423435
abs_timeout_time is None or (backoff + time.time()) <= abs_timeout_time
424436
): # pylint:disable=no-else-return

sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
from typing import Optional, Dict, Any
66

77
from uamqp.constants import TransportType
8+
from .._retry import RetryMode
89

910

1011
class Configuration(object): # pylint:disable=too-many-instance-attributes
1112
def __init__(self, **kwargs):
1213
self.user_agent = kwargs.get("user_agent") # type: Optional[str]
1314
self.retry_total = kwargs.get("retry_total", 3) # type: int
15+
self.retry_mode = kwargs.get("retry_mode", RetryMode.EXPONENTIAL)
1416
self.retry_backoff_factor = kwargs.get(
1517
"retry_backoff_factor", 0.8
1618
) # type: float
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
from typing import Optional, Dict, Any
6+
7+
from enum import Enum
8+
9+
class RetryMode(str, Enum):
10+
EXPONENTIAL = 'exponential'
11+
FIXED = 'fixed'

sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py

+9
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class ServiceBusClient(object):
6060
:keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries.
6161
Default value is 0.8.
6262
:keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120.
63+
:keyword retry_mode: Fixed or exponential delay between attempts, default is exponential.
64+
:paramtype retry_mode: ~azure.servicebus.RetryMode
6365
6466
.. admonition:: Example:
6567
@@ -152,6 +154,8 @@ def from_connection_string(cls, conn_str, **kwargs):
152154
:keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries.
153155
Default value is 0.8.
154156
:keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120.
157+
:keyword retry_mode: Fixed or exponential delay between attempts, default is exponential.
158+
:paramtype retry_mode: ~azure.servicebus.RetryMode
155159
:rtype: ~azure.servicebus.ServiceBusClient
156160
157161
.. admonition:: Example:
@@ -212,6 +216,7 @@ def get_queue_sender(self, queue_name, **kwargs):
212216
http_proxy=self._config.http_proxy,
213217
connection=self._connection,
214218
user_agent=self._config.user_agent,
219+
retry_mode=self._config.retry_mode,
215220
retry_total=self._config.retry_total,
216221
retry_backoff_factor=self._config.retry_backoff_factor,
217222
retry_backoff_max=self._config.retry_backoff_max,
@@ -303,6 +308,7 @@ def get_queue_receiver(self, queue_name, **kwargs):
303308
http_proxy=self._config.http_proxy,
304309
connection=self._connection,
305310
user_agent=self._config.user_agent,
311+
retry_mode=self._config.retry_mode,
306312
retry_total=self._config.retry_total,
307313
retry_backoff_factor=self._config.retry_backoff_factor,
308314
retry_backoff_max=self._config.retry_backoff_max,
@@ -344,6 +350,7 @@ def get_topic_sender(self, topic_name, **kwargs):
344350
http_proxy=self._config.http_proxy,
345351
connection=self._connection,
346352
user_agent=self._config.user_agent,
353+
retry_mode=self._config.retry_mode,
347354
retry_total=self._config.retry_total,
348355
retry_backoff_factor=self._config.retry_backoff_factor,
349356
retry_backoff_max=self._config.retry_backoff_max,
@@ -433,6 +440,7 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs):
433440
http_proxy=self._config.http_proxy,
434441
connection=self._connection,
435442
user_agent=self._config.user_agent,
443+
retry_mode=self._config.retry_mode,
436444
retry_total=self._config.retry_total,
437445
retry_backoff_factor=self._config.retry_backoff_factor,
438446
retry_backoff_max=self._config.retry_backoff_max,
@@ -453,6 +461,7 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs):
453461
http_proxy=self._config.http_proxy,
454462
connection=self._connection,
455463
user_agent=self._config.user_agent,
464+
retry_mode=self._config.retry_mode,
456465
retry_total=self._config.retry_total,
457466
retry_backoff_factor=self._config.retry_backoff_factor,
458467
retry_backoff_max=self._config.retry_backoff_max,

sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from azure.core.credentials import AccessToken, AzureSasCredential, AzureNamedKeyCredential
1616

17-
from .._base_handler import _generate_sas_token, BaseHandler as BaseHandlerSync
17+
from .._base_handler import _generate_sas_token, BaseHandler as BaseHandlerSync, _get_backoff_time
1818
from .._common._configuration import Configuration
1919
from .._common.utils import create_properties, strip_protocol_from_uri, parse_sas_credential
2020
from .._common.constants import (
@@ -268,7 +268,12 @@ async def _backoff(
268268
self, retried_times, last_exception, abs_timeout_time=None, entity_name=None
269269
):
270270
entity_name = entity_name or self._container_id
271-
backoff = self._config.retry_backoff_factor * 2 ** retried_times
271+
backoff = _get_backoff_time(
272+
self._config.retry_mode,
273+
self._config.retry_backoff_factor,
274+
self._config.retry_backoff_max,
275+
retried_times,
276+
)
272277
if backoff <= self._config.retry_backoff_max and (
273278
abs_timeout_time is None or (backoff + time.time()) <= abs_timeout_time
274279
):

sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py

+9
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class ServiceBusClient(object):
5858
:keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries.
5959
Default value is 0.8.
6060
:keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120.
61+
:keyword retry_mode: Fixed or exponential delay between attempts, default is exponential.
62+
:paramtype retry_mode: ~azure.eventhub.RetryMode
6163
6264
.. admonition:: Example:
6365
@@ -129,6 +131,8 @@ def from_connection_string(cls, conn_str: str, **kwargs: Any) -> "ServiceBusClie
129131
:keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries.
130132
Default value is 0.8.
131133
:keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120.
134+
:keyword retry_mode: Fixed or exponential delay between attempts, default is exponential.
135+
:paramtype retry_mode: ~azure.eventhub.RetryMode
132136
:rtype: ~azure.servicebus.aio.ServiceBusClient
133137
134138
.. admonition:: Example:
@@ -209,6 +213,7 @@ def get_queue_sender(self, queue_name: str, **kwargs: Any) -> ServiceBusSender:
209213
http_proxy=self._config.http_proxy,
210214
connection=self._connection,
211215
user_agent=self._config.user_agent,
216+
retry_mode=self._config.retry_mode,
212217
retry_total=self._config.retry_total,
213218
retry_backoff_factor=self._config.retry_backoff_factor,
214219
retry_backoff_max=self._config.retry_backoff_max,
@@ -298,6 +303,7 @@ def get_queue_receiver(self, queue_name: str, **kwargs: Any) -> ServiceBusReceiv
298303
http_proxy=self._config.http_proxy,
299304
connection=self._connection,
300305
user_agent=self._config.user_agent,
306+
retry_mode=self._config.retry_mode,
301307
retry_total=self._config.retry_total,
302308
retry_backoff_factor=self._config.retry_backoff_factor,
303309
retry_backoff_max=self._config.retry_backoff_max,
@@ -338,6 +344,7 @@ def get_topic_sender(self, topic_name: str, **kwargs: Any) -> ServiceBusSender:
338344
http_proxy=self._config.http_proxy,
339345
connection=self._connection,
340346
user_agent=self._config.user_agent,
347+
retry_mode=self._config.retry_mode,
341348
retry_total=self._config.retry_total,
342349
retry_backoff_factor=self._config.retry_backoff_factor,
343350
retry_backoff_max=self._config.retry_backoff_max,
@@ -428,6 +435,7 @@ def get_subscription_receiver(
428435
http_proxy=self._config.http_proxy,
429436
connection=self._connection,
430437
user_agent=self._config.user_agent,
438+
retry_mode=self._config.retry_mode,
431439
retry_total=self._config.retry_total,
432440
retry_backoff_factor=self._config.retry_backoff_factor,
433441
retry_backoff_max=self._config.retry_backoff_max,
@@ -448,6 +456,7 @@ def get_subscription_receiver(
448456
http_proxy=self._config.http_proxy,
449457
connection=self._connection,
450458
user_agent=self._config.user_agent,
459+
retry_mode=self._config.retry_mode,
451460
retry_total=self._config.retry_total,
452461
retry_backoff_factor=self._config.retry_backoff_factor,
453462
retry_backoff_max=self._config.retry_backoff_max,

sdk/servicebus/azure-servicebus/tests/async_tests/test_sb_client_async.py

+42
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
import logging
9+
import time
910
import pytest
1011

1112
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential
@@ -401,3 +402,44 @@ async def test_client_named_key_credential_async(self,
401402
async with client:
402403
async with client.get_queue_sender(servicebus_queue.name) as sender:
403404
await sender.send_messages(ServiceBusMessage("foo"))
405+
406+
async def test_backoff_fixed_retry(self):
407+
client = ServiceBusClient(
408+
'fake.host.com',
409+
'fake_eh',
410+
retry_mode=RetryMode.FIXED
411+
)
412+
# queue sender
413+
sender = await client.get_queue_sender('fake_name')
414+
backoff = client._config.retry_backoff_factor
415+
start_time = time.time()
416+
sender._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
417+
sleep_time_fixed = time.time() - start_time
418+
# exp = 0.8 * (2 ** 1) = 1.6
419+
# time.sleep() in _backoff will take AT LEAST time 'exp' for RetryMode.EXPONENTIAL
420+
# check that fixed is less than 'exp'
421+
assert sleep_time_fixed < backoff * (2 ** 1)
422+
423+
# topic sender
424+
sender = await client.get_topic_sender('fake_name')
425+
backoff = client._config.retry_backoff_factor
426+
start_time = time.time()
427+
sender._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
428+
sleep_time_fixed = time.time() - start_time
429+
assert sleep_time_fixed < backoff * (2 ** 1)
430+
431+
# queue receiver
432+
receiver = await client.get_queue_receiver('fake_name')
433+
backoff = client._config.retry_backoff_factor
434+
start_time = time.time()
435+
receiver._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
436+
sleep_time_fixed = time.time() - start_time
437+
assert sleep_time_fixed < backoff * (2 ** 1)
438+
439+
# subscription receiver
440+
receiver = await client.get_subscription_receiver('fake_topic', 'fake_sub')
441+
backoff = client._config.retry_backoff_factor
442+
start_time = time.time()
443+
receiver._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
444+
sleep_time_fixed = time.time() - start_time
445+
assert sleep_time_fixed < backoff * (2 ** 1)

sdk/servicebus/azure-servicebus/tests/test_sb_client.py

+42-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from azure.common import AzureHttpError, AzureConflictHttpError
1515
from azure.core.credentials import AzureSasCredential, AzureNamedKeyCredential
1616
from azure.mgmt.servicebus.models import AccessRights
17-
from azure.servicebus import ServiceBusClient, ServiceBusSender, ServiceBusReceiver
17+
from azure.servicebus import ServiceBusClient, ServiceBusSender, ServiceBusReceiver, RetryMode
1818
from azure.servicebus._base_handler import ServiceBusSharedKeyCredential
1919
from azure.servicebus._common.message import ServiceBusMessage, ServiceBusReceivedMessage
2020
from azure.servicebus.exceptions import (
@@ -419,3 +419,44 @@ def test_client_azure_named_key_credential(self,
419419
with client:
420420
with client.get_queue_sender(servicebus_queue.name) as sender:
421421
sender.send_messages(ServiceBusMessage("foo"))
422+
423+
def test_backoff_fixed_retry(self):
424+
client = ServiceBusClient(
425+
'fake.host.com',
426+
'fake_eh',
427+
retry_mode=RetryMode.FIXED
428+
)
429+
# queue sender
430+
sender = client.get_queue_sender('fake_name')
431+
backoff = client._config.retry_backoff_factor
432+
start_time = time.time()
433+
sender._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
434+
sleep_time_fixed = time.time() - start_time
435+
# exp = 0.8 * (2 ** 1) = 1.6
436+
# time.sleep() in _backoff will take AT LEAST time 'exp' for RetryMode.EXPONENTIAL
437+
# check that fixed is less than 'exp'
438+
assert sleep_time_fixed < backoff * (2 ** 1)
439+
440+
# topic sender
441+
sender = client.get_topic_sender('fake_name')
442+
backoff = client._config.retry_backoff_factor
443+
start_time = time.time()
444+
sender._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
445+
sleep_time_fixed = time.time() - start_time
446+
assert sleep_time_fixed < backoff * (2 ** 1)
447+
448+
# queue receiver
449+
receiver = client.get_queue_receiver('fake_name')
450+
backoff = client._config.retry_backoff_factor
451+
start_time = time.time()
452+
receiver._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
453+
sleep_time_fixed = time.time() - start_time
454+
assert sleep_time_fixed < backoff * (2 ** 1)
455+
456+
# subscription receiver
457+
receiver = client.get_subscription_receiver('fake_topic', 'fake_sub')
458+
backoff = client._config.retry_backoff_factor
459+
start_time = time.time()
460+
receiver._backoff(retried_times=1, last_exception=Exception('fake'), abs_timeout_time=None)
461+
sleep_time_fixed = time.time() - start_time
462+
assert sleep_time_fixed < backoff * (2 ** 1)

0 commit comments

Comments
 (0)