From c6c4d5c44a9de3b60ee11e2358cd048edccc1d21 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Wed, 16 Sep 2020 20:52:46 -0700 Subject: [PATCH 01/18] sync opeartion timeout update --- .../azure/servicebus/_base_handler.py | 42 ++++++++++++++----- .../servicebus/_common/_configuration.py | 1 + .../azure/servicebus/_common/message.py | 13 +++--- .../azure/servicebus/_servicebus_client.py | 13 ++++++ .../azure/servicebus/_servicebus_receiver.py | 37 +++++++++------- .../azure/servicebus/_servicebus_sender.py | 28 +++++++++---- .../azure/servicebus/_servicebus_session.py | 41 ++++++++++++------ 7 files changed, 125 insertions(+), 50 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index 545fa074807..7793e4138e7 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -233,14 +233,14 @@ def _backoff( self, retried_times, last_exception, - timeout=None, + abs_timeout=None, entity_name=None ): # type: (int, Exception, Optional[float], str) -> None entity_name = entity_name or self._container_id backoff = self._config.retry_backoff_factor * 2 ** retried_times if backoff <= self._config.retry_backoff_max and ( - timeout is None or backoff <= timeout + abs_timeout is None or (backoff + time.time()) <= abs_timeout ): # pylint:disable=no-else-return time.sleep(backoff) _LOGGER.info( @@ -263,10 +263,13 @@ def _do_retryable_operation(self, operation, timeout=None, **kwargs): retried_times = 0 max_retries = self._config.retry_total + abs_timeout = (time.time() + timeout) if (require_timeout and timeout) else None + while retried_times <= max_retries: try: - if require_timeout: - kwargs["timeout"] = timeout + if require_timeout and abs_timeout: + remaining_timeout = abs_timeout - time.time() + kwargs["timeout"] = remaining_timeout return operation(**kwargs) except StopIteration: raise @@ -285,13 +288,22 @@ def _do_retryable_operation(self, operation, timeout=None, **kwargs): self._backoff( retried_times=retried_times, last_exception=last_exception, - timeout=timeout + abs_timeout=abs_timeout ) - def _mgmt_request_response(self, mgmt_operation, message, callback, keep_alive_associated_link=True, **kwargs): - # type: (str, uamqp.Message, Callable, bool, Any) -> uamqp.Message + def _mgmt_request_response( + self, + mgmt_operation, + message, + callback, + keep_alive_associated_link=True, + timeout=5, + **kwargs + ): + # type: (bytes, uamqp.Message, Callable, bool, float, Any) -> uamqp.Message self._open() application_properties = {} + # Some mgmt calls do not support an associated link name (such as list_sessions). Most do, so on by default. if keep_alive_associated_link: try: @@ -314,19 +326,29 @@ def _mgmt_request_response(self, mgmt_operation, message, callback, keep_alive_a mgmt_operation, op_type=MGMT_REQUEST_OP_TYPE_ENTITY_MGMT, node=self._mgmt_target.encode(self._config.encoding), - timeout=5000, + timeout=timeout * 1000, callback=callback ) except Exception as exp: # pylint: disable=broad-except raise ServiceBusError("Management request failed: {}".format(exp), exp) - def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, **kwargs): - # type: (bytes, Dict[str, Any], Callable, Any) -> Any + def _mgmt_request_response_with_retry( + self, + mgmt_operation, + message, + callback, + timeout=None, + **kwargs + ): + # type: (bytes, Dict[str, Any], Callable, float, Any) -> Any + timeout = timeout or self._config.timeout return self._do_retryable_operation( self._mgmt_request_response, mgmt_operation=mgmt_operation, message=message, callback=callback, + timeout=timeout, + require_timeout=True, **kwargs ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py index 996768b4bcd..5a5356d2986 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py @@ -13,6 +13,7 @@ def __init__(self, **kwargs): self.retry_total = kwargs.get("retry_total", 3) # type: int self.retry_backoff_factor = kwargs.get("retry_backoff_factor", 0.8) # type: float self.retry_backoff_max = kwargs.get("retry_backoff_max", 120) # type: int + self.timeout = kwargs.get("timeout", 60) # type: float self.logging_enable = kwargs.get("logging_enable", False) # type: bool self.http_proxy = kwargs.get("http_proxy") # type: Optional[Dict[str, Any]] self.transport_type = ( diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py index 856bd31638f..e48eb53da08 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py @@ -1042,8 +1042,8 @@ def defer(self): self._settle_message(MESSAGE_DEFER) self._settled = True - def renew_lock(self): - # type: () -> datetime.datetime + def renew_lock(self, **kwargs): + # type: (Any) -> datetime.datetime # pylint: disable=protected-access,no-member """Renew the message lock. @@ -1059,6 +1059,8 @@ def renew_lock(self): Lock renewal can be performed as a background task by registering the message with an `azure.servicebus.AutoLockRenew` instance. + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime :raises: TypeError if the message is sessionful. @@ -1066,7 +1068,7 @@ def renew_lock(self): :raises: ~azure.servicebus.exceptions.MessageAlreadySettled is message has already been settled. """ try: - if self._receiver.session: # type: ignore + if self._receiver.session: # type: ignore raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") except AttributeError: pass @@ -1075,8 +1077,9 @@ def renew_lock(self): if not token: raise ValueError("Unable to renew lock - no lock token found.") - expiry = self._receiver._renew_locks(token) # type: ignore - self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime + timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + expiry = self._receiver._renew_locks(token, timeout=timeout) # type: ignore + self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime return self._expiry diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py index 8fcbc9efcaf..83ad90411b8 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py @@ -52,6 +52,9 @@ class ServiceBusClient(object): :keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries. Default value is 0.8. :keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120. + :keyword float timeout: Timeout setting for the operation in the unit of second, Default value is 60. + This timeout doesn't apply to `ServiceBusReceiver.receive` method and the iterator approach on ServiceBusReceiver + as they have dedicated parameters for timeout control. .. admonition:: Example: @@ -145,6 +148,9 @@ def from_connection_string( :keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries. Default value is 0.8. :keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120. + :keyword float timeout: Timeout setting for the operation in the unit of second, Default value is 60. + This timeout doesn't apply to `ServiceBusReceiver.receive` method and the iterator approach on ServiceBusReceiver + as they have dedicated parameters for timeout control. :rtype: ~azure.servicebus.ServiceBusClient .. admonition:: Example: @@ -199,6 +205,7 @@ def get_queue_sender(self, queue_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -259,6 +266,7 @@ def get_queue_receiver(self, queue_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -293,6 +301,7 @@ def get_topic_sender(self, topic_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -356,6 +365,7 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) else: @@ -372,6 +382,7 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -430,6 +441,7 @@ def get_subscription_session_receiver(self, topic_name, subscription_name, sessi retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -486,6 +498,7 @@ def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, + timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py index 9341fbc87ba..0e78c8db31b 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -210,11 +210,6 @@ def _open(self): self.close() raise - def close(self): - # type: () -> None - super(ServiceBusReceiver, self).close() - self._message_iter = None # pylint: disable=attribute-defined-outside-init - def _receive(self, max_message_count=None, timeout=None): # type: (Optional[int], Optional[float]) -> List[ReceivedMessage] # pylint: disable=protected-access @@ -276,15 +271,21 @@ def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): mgmt_handlers.default ) - def _renew_locks(self, *lock_tokens): - # type: (str) -> Any + def _renew_locks(self, *lock_tokens, timeout=None): + # type: (str, Any) -> Any message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEWLOCK_OPERATION, message, - mgmt_handlers.lock_renew_op + mgmt_handlers.lock_renew_op, + timeout=timeout ) + def close(self): + # type: () -> None + super(ServiceBusReceiver, self).close() + self._message_iter = None # pylint: disable=attribute-defined-outside-init + def get_streaming_message_iter(self, max_wait_time=None): # type: (float) -> Iterator[ReceivedMessage] """Receive messages from an iterator indefinitely, or if a max_wait_time is specified, until @@ -416,8 +417,8 @@ def receive_messages(self, max_message_count=None, max_wait_time=None): require_timeout=True ) - def receive_deferred_messages(self, sequence_numbers): - # type: (Union[int,List[int]]) -> List[ReceivedMessage] + def receive_deferred_messages(self, sequence_numbers, **kwargs): + # type: (Union[int,List[int]], Any) -> List[ReceivedMessage] """Receive messages that have previously been deferred. When receiving deferred messages from a partitioned entity, all of the supplied @@ -425,6 +426,8 @@ def receive_deferred_messages(self, sequence_numbers): :param Union[int,List[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :rtype: List[~azure.servicebus.ReceivedMessage] .. admonition:: Example: @@ -438,6 +441,7 @@ def receive_deferred_messages(self, sequence_numbers): """ self._check_live() + timeout = kwargs.pop("timeout", None) or self._config.timeout if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] if not sequence_numbers: @@ -458,12 +462,13 @@ def receive_deferred_messages(self, sequence_numbers): messages = self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, - handler + handler, + timeout=timeout ) return messages - def peek_messages(self, max_message_count=1, sequence_number=None): - # type: (int, Optional[int]) -> List[PeekedMessage] + def peek_messages(self, max_message_count=1, sequence_number=None, **kwargs): + # type: (int, Optional[int], Any) -> List[PeekedMessage] """Browse messages currently pending in the queue. Peeked messages are not removed from queue, nor are they locked. They cannot be completed, @@ -472,6 +477,8 @@ def peek_messages(self, max_message_count=1, sequence_number=None): :param int max_message_count: The maximum number of messages to try and peek. The default value is 1. :param int sequence_number: A message sequence number from which to start browsing messages. + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :rtype: List[~azure.servicebus.PeekedMessage] @@ -486,6 +493,7 @@ def peek_messages(self, max_message_count=1, sequence_number=None): """ self._check_live() + timeout = kwargs.pop("timeout", None) or self._config.timeout if not sequence_number: sequence_number = self._last_received_sequenced_number or 1 if int(max_message_count) < 1: @@ -504,5 +512,6 @@ def peek_messages(self, max_message_count=1, sequence_number=None): return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_PEEK_OPERATION, message, - mgmt_handlers.peek_op + mgmt_handlers.peek_op, + timeout=timeout ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index d664f1165ae..cad5f730a7c 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -191,14 +191,16 @@ def _send(self, message, timeout=None, last_exception=None): self._set_msg_timeout(timeout, last_exception) self._handler.send_message(message.message) - def schedule_messages(self, messages, schedule_time_utc): - # type: (Union[Message, List[Message]], datetime.datetime) -> List[int] + def schedule_messages(self, messages, schedule_time_utc, **kwargs): + # type: (Union[Message, List[Message]], datetime.datetime, Any) -> List[int] """Send Message or multiple Messages to be enqueued at a specific time. Returns a list of the sequence numbers of the enqueued messages. :param messages: The message or list of messages to schedule. :type messages: Union[~azure.servicebus.Message, List[~azure.servicebus.Message]] :param schedule_time_utc: The utc date and time to enqueue the messages. :type schedule_time_utc: ~datetime.datetime + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :rtype: List[int] .. admonition:: Example: @@ -212,6 +214,7 @@ def schedule_messages(self, messages, schedule_time_utc): """ # pylint: disable=protected-access self._open() + timeout = kwargs.pop("timeout", None) or self._config.timeout if isinstance(messages, Message): request_body = self._build_schedule_request(schedule_time_utc, messages) else: @@ -219,16 +222,19 @@ def schedule_messages(self, messages, schedule_time_utc): return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, request_body, - mgmt_handlers.schedule_op + mgmt_handlers.schedule_op, + timeout=timeout ) - def cancel_scheduled_messages(self, sequence_numbers): - # type: (Union[int, List[int]]) -> None + def cancel_scheduled_messages(self, sequence_numbers, **kwargs): + # type: (Union[int, List[int]], Any) -> None """ Cancel one or more messages that have previously been scheduled and are still pending. :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. @@ -243,6 +249,7 @@ def cancel_scheduled_messages(self, sequence_numbers): :caption: Cancelling messages scheduled to be sent in future """ self._open() + timeout = kwargs.pop("timeout", None) or self._config.timeout if isinstance(sequence_numbers, int): numbers = [types.AMQPLong(sequence_numbers)] else: @@ -251,7 +258,8 @@ def cancel_scheduled_messages(self, sequence_numbers): return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, request_body, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) @classmethod @@ -299,8 +307,8 @@ def from_connection_string( ) return cls(**constructor_args) - def send_messages(self, message): - # type: (Union[Message, BatchMessage, List[Message]]) -> None + def send_messages(self, message, **kwargs): + # type: (Union[Message, BatchMessage, List[Message]], Any) -> None """Sends message and blocks until acknowledgement is received or operation times out. If a list of messages was provided, attempts to send them as a single batch, throwing a @@ -308,6 +316,8 @@ def send_messages(self, message): :param message: The ServiceBus message to be sent. :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage or list[~azure.servicebus.Message] + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :rtype: None :raises: :class: ~azure.servicebus.exceptions.OperationTimeoutError if sending times out. @@ -328,6 +338,7 @@ def send_messages(self, message): :caption: Send message. """ + timeout = kwargs.pop("timeout", None) or self._config.timeout message = transform_messages_to_sendable_if_needed(message) try: batch = self.create_batch() @@ -343,6 +354,7 @@ def send_messages(self, message): self._do_retryable_operation( self._send, message=message, + timeout=timeout, require_timeout=True, require_last_exception=True ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py index 76a1e094806..9a60a754620 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- import logging import datetime -from typing import TYPE_CHECKING, Union, Optional +from typing import TYPE_CHECKING, Union, Optional, Any import six from ._common.utils import utc_from_timestamp, utc_now @@ -90,12 +90,15 @@ class ServiceBusSession(BaseSession): :caption: Get session from a receiver """ - def get_state(self): - # type: () -> str + def get_state(self, **kwargs): + # type: (Any) -> str + # pylint: disable=protected-access """Get the session state. Returns None if no state has been set. + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :rtype: str .. admonition:: Example: @@ -108,22 +111,27 @@ def get_state(self): :caption: Get the session state """ self._check_live() - response = self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + response = self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id}, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) session_state = response.get(MGMT_RESPONSE_SESSION_STATE) if isinstance(session_state, six.binary_type): session_state = session_state.decode(self._encoding) return session_state - def set_state(self, state): - # type: (Union[str, bytes, bytearray]) -> None + def set_state(self, state, **kwargs): + # type: (Union[str, bytes, bytearray], Any) -> None + # pylint: disable=protected-access """Set the session state. :param state: The state value. :type state: Union[str, bytes, bytearray] + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. .. admonition:: Example: @@ -135,15 +143,18 @@ def set_state(self, state): :caption: Set the session state """ self._check_live() + timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout state = state.encode(self._encoding) if isinstance(state, six.text_type) else state - return self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + return self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id, MGMT_REQUEST_SESSION_STATE: bytearray(state)}, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) - def renew_lock(self): - # type: () -> datetime.datetime + def renew_lock(self, **kwargs): + # type: (Any) -> datetime.datetime + # pylint: disable=protected-access """Renew the session lock. This operation must be performed periodically in order to retain a lock on the @@ -154,6 +165,8 @@ def renew_lock(self): This operation can also be performed as a threaded background task by registering the session with an `azure.servicebus.AutoLockRenew` instance. + :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client + setting would be used which by default is 60s. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime @@ -167,10 +180,12 @@ def renew_lock(self): :caption: Renew the session lock before it expires """ self._check_live() - expiry = self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access + timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + expiry = self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id}, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) expiry_timestamp = expiry[MGMT_RESPONSE_RECEIVER_EXPIRATION]/1000.0 self._locked_until_utc = utc_from_timestamp(expiry_timestamp) # type: datetime.datetime From 3feb7874b71e79520834f49276fc1b970b4fa625 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Thu, 17 Sep 2020 16:30:48 -0700 Subject: [PATCH 02/18] add support for operation timeout to certain methods --- sdk/servicebus/azure-servicebus/CHANGELOG.md | 8 ++++++ .../azure/servicebus/_base_handler.py | 14 +++++----- .../servicebus/_common/_configuration.py | 2 ++ .../azure/servicebus/_common/message.py | 5 ++-- .../azure/servicebus/_servicebus_client.py | 13 ---------- .../azure/servicebus/_servicebus_receiver.py | 12 ++++----- .../azure/servicebus/_servicebus_sender.py | 15 +++++------ .../azure/servicebus/_servicebus_session.py | 15 +++++------ .../_servicebus_session_receiver.py | 2 +- .../azure/servicebus/aio/_async_message.py | 5 ++-- .../servicebus/aio/_base_handler_async.py | 23 ++++++++++------ .../aio/_servicebus_client_async.py | 4 +-- .../aio/_servicebus_receiver_async.py | 24 ++++++++++------- .../aio/_servicebus_sender_async.py | 24 ++++++++++------- .../aio/_servicebus_session_async.py | 26 ++++++++++++------- 15 files changed, 103 insertions(+), 89 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index 5e9f5001b5e..8d27182627c 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -2,6 +2,14 @@ ## 7.0.0b7 (Unreleased) +**New Features** + +* Added support for timeout on operations: + - `ServiceBusSender`: `send_messages`, `schedule_messages` and `cancel_scheduled_messages` + - `ServiceBusReceiver`: `receive_deferred_messages` and `peek_messages` + - `ServiceBusSession`: `get_state`, `set_state` and `renew_lock` + - `ReceivedMessage`: `renew_lock` + ## 7.0.0b6 (2020-09-10) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index 7793e4138e7..af3c7e3203d 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -233,14 +233,14 @@ def _backoff( self, retried_times, last_exception, - abs_timeout=None, + abs_timeout_time=None, entity_name=None ): # type: (int, Exception, Optional[float], str) -> None entity_name = entity_name or self._container_id backoff = self._config.retry_backoff_factor * 2 ** retried_times if backoff <= self._config.retry_backoff_max and ( - abs_timeout is None or (backoff + time.time()) <= abs_timeout + abs_timeout_time is None or (backoff + time.time()) <= abs_timeout_time ): # pylint:disable=no-else-return time.sleep(backoff) _LOGGER.info( @@ -263,12 +263,12 @@ def _do_retryable_operation(self, operation, timeout=None, **kwargs): retried_times = 0 max_retries = self._config.retry_total - abs_timeout = (time.time() + timeout) if (require_timeout and timeout) else None + abs_timeout_time = (time.time() + timeout) if (require_timeout and timeout) else None while retried_times <= max_retries: try: - if require_timeout and abs_timeout: - remaining_timeout = abs_timeout - time.time() + if require_timeout and abs_timeout_time: + remaining_timeout = abs_timeout_time - time.time() kwargs["timeout"] = remaining_timeout return operation(**kwargs) except StopIteration: @@ -288,7 +288,7 @@ def _do_retryable_operation(self, operation, timeout=None, **kwargs): self._backoff( retried_times=retried_times, last_exception=last_exception, - abs_timeout=abs_timeout + abs_timeout_time=abs_timeout_time ) def _mgmt_request_response( @@ -337,7 +337,7 @@ def _mgmt_request_response_with_retry( mgmt_operation, message, callback, - timeout=None, + timeout=5, **kwargs ): # type: (bytes, Dict[str, Any], Callable, float, Any) -> Any diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py index 5a5356d2986..8005ae5c24e 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py @@ -21,7 +21,9 @@ def __init__(self, **kwargs): if self.http_proxy else kwargs.get("transport_type", TransportType.Amqp) ) + # The following configs are not public, for internal usage only self.auth_timeout = kwargs.get("auth_timeout", 60) # type: int self.encoding = kwargs.get("encoding", "UTF-8") self.auto_reconnect = kwargs.get("auto_reconnect", True) self.keep_alive = kwargs.get("keep_alive", 30) + self.timeout = kwargs.get("timeout", None) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py index e48eb53da08..75c80013818 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py @@ -1059,8 +1059,7 @@ def renew_lock(self, **kwargs): Lock renewal can be performed as a background task by registering the message with an `azure.servicebus.AutoLockRenew` instance. - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime :raises: TypeError if the message is sessionful. @@ -1077,7 +1076,7 @@ def renew_lock(self, **kwargs): if not token: raise ValueError("Unable to renew lock - no lock token found.") - timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + timeout = kwargs.pop("timeout", None) expiry = self._receiver._renew_locks(token, timeout=timeout) # type: ignore self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py index 83ad90411b8..8fcbc9efcaf 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_client.py @@ -52,9 +52,6 @@ class ServiceBusClient(object): :keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries. Default value is 0.8. :keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120. - :keyword float timeout: Timeout setting for the operation in the unit of second, Default value is 60. - This timeout doesn't apply to `ServiceBusReceiver.receive` method and the iterator approach on ServiceBusReceiver - as they have dedicated parameters for timeout control. .. admonition:: Example: @@ -148,9 +145,6 @@ def from_connection_string( :keyword float retry_backoff_factor: Delta back-off internal in the unit of second between retries. Default value is 0.8. :keyword float retry_backoff_max: Maximum back-off interval in the unit of second. Default value is 120. - :keyword float timeout: Timeout setting for the operation in the unit of second, Default value is 60. - This timeout doesn't apply to `ServiceBusReceiver.receive` method and the iterator approach on ServiceBusReceiver - as they have dedicated parameters for timeout control. :rtype: ~azure.servicebus.ServiceBusClient .. admonition:: Example: @@ -205,7 +199,6 @@ def get_queue_sender(self, queue_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -266,7 +259,6 @@ def get_queue_receiver(self, queue_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -301,7 +293,6 @@ def get_topic_sender(self, topic_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -365,7 +356,6 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) else: @@ -382,7 +372,6 @@ def get_subscription_receiver(self, topic_name, subscription_name, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -441,7 +430,6 @@ def get_subscription_session_receiver(self, topic_name, subscription_name, sessi retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) @@ -498,7 +486,6 @@ def get_queue_session_receiver(self, queue_name, session_id=None, **kwargs): retry_total=self._config.retry_total, retry_backoff_factor=self._config.retry_backoff_factor, retry_backoff_max=self._config.retry_backoff_max, - timeout=self._config.timeout, **kwargs ) self._handlers.append(handler) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py index 0e78c8db31b..2b0e51eaf07 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -272,7 +272,7 @@ def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): ) def _renew_locks(self, *lock_tokens, timeout=None): - # type: (str, Any) -> Any + # type: (str, Optional[float]) -> Any message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEWLOCK_OPERATION, @@ -426,8 +426,7 @@ def receive_deferred_messages(self, sequence_numbers, **kwargs): :param Union[int,List[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: List[~azure.servicebus.ReceivedMessage] .. admonition:: Example: @@ -441,7 +440,7 @@ def receive_deferred_messages(self, sequence_numbers, **kwargs): """ self._check_live() - timeout = kwargs.pop("timeout", None) or self._config.timeout + timeout = kwargs.pop("timeout", None) if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] if not sequence_numbers: @@ -477,8 +476,7 @@ def peek_messages(self, max_message_count=1, sequence_number=None, **kwargs): :param int max_message_count: The maximum number of messages to try and peek. The default value is 1. :param int sequence_number: A message sequence number from which to start browsing messages. - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: List[~azure.servicebus.PeekedMessage] @@ -493,7 +491,7 @@ def peek_messages(self, max_message_count=1, sequence_number=None, **kwargs): """ self._check_live() - timeout = kwargs.pop("timeout", None) or self._config.timeout + timeout = kwargs.pop("timeout", None) if not sequence_number: sequence_number = self._last_received_sequenced_number or 1 if int(max_message_count) < 1: diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index cad5f730a7c..43735cd6c91 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -199,8 +199,7 @@ def schedule_messages(self, messages, schedule_time_utc, **kwargs): :type messages: Union[~azure.servicebus.Message, List[~azure.servicebus.Message]] :param schedule_time_utc: The utc date and time to enqueue the messages. :type schedule_time_utc: ~datetime.datetime - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: List[int] .. admonition:: Example: @@ -214,7 +213,7 @@ def schedule_messages(self, messages, schedule_time_utc, **kwargs): """ # pylint: disable=protected-access self._open() - timeout = kwargs.pop("timeout", None) or self._config.timeout + timeout = kwargs.pop("timeout", None) if isinstance(messages, Message): request_body = self._build_schedule_request(schedule_time_utc, messages) else: @@ -233,8 +232,7 @@ def cancel_scheduled_messages(self, sequence_numbers, **kwargs): :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. @@ -249,7 +247,7 @@ def cancel_scheduled_messages(self, sequence_numbers, **kwargs): :caption: Cancelling messages scheduled to be sent in future """ self._open() - timeout = kwargs.pop("timeout", None) or self._config.timeout + timeout = kwargs.pop("timeout", None) if isinstance(sequence_numbers, int): numbers = [types.AMQPLong(sequence_numbers)] else: @@ -316,8 +314,7 @@ def send_messages(self, message, **kwargs): :param message: The ServiceBus message to be sent. :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage or list[~azure.servicebus.Message] - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: None :raises: :class: ~azure.servicebus.exceptions.OperationTimeoutError if sending times out. @@ -338,7 +335,7 @@ def send_messages(self, message, **kwargs): :caption: Send message. """ - timeout = kwargs.pop("timeout", None) or self._config.timeout + timeout = kwargs.pop("timeout", None) message = transform_messages_to_sendable_if_needed(message) try: batch = self.create_batch() diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py index 9a60a754620..a837a2238d8 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py @@ -97,8 +97,7 @@ def get_state(self, **kwargs): Returns None if no state has been set. - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: str .. admonition:: Example: @@ -111,7 +110,7 @@ def get_state(self, **kwargs): :caption: Get the session state """ self._check_live() - timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + timeout = kwargs.pop("timeout", None) response = self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id}, @@ -130,8 +129,7 @@ def set_state(self, state, **kwargs): :param state: The state value. :type state: Union[str, bytes, bytearray] - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. .. admonition:: Example: @@ -143,7 +141,7 @@ def set_state(self, state, **kwargs): :caption: Set the session state """ self._check_live() - timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + timeout = kwargs.pop("timeout", None) state = state.encode(self._encoding) if isinstance(state, six.text_type) else state return self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, @@ -165,8 +163,7 @@ def renew_lock(self, **kwargs): This operation can also be performed as a threaded background task by registering the session with an `azure.servicebus.AutoLockRenew` instance. - :keyword float timeout: The operation timeout in seconds. If not specified, the timeout value in the client - setting would be used which by default is 60s. + :keyword float timeout: The total operation timeout in seconds including all the retries. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime @@ -180,7 +177,7 @@ def renew_lock(self, **kwargs): :caption: Renew the session lock before it expires """ self._check_live() - timeout = kwargs.pop("timeout", None) or self._receiver._config.timeout + timeout = kwargs.pop("timeout", None) expiry = self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id}, diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py index 51381600da6..1799c5059e1 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session_receiver.py @@ -10,11 +10,11 @@ from ._servicebus_session import ServiceBusSession if TYPE_CHECKING: - import datetime from azure.core.credentials import TokenCredential _LOGGER = logging.getLogger(__name__) + class ServiceBusSessionReceiver(ServiceBusReceiver, SessionReceiverMixin): """The ServiceBusSessionReceiver class defines a high level interface for receiving messages from the Azure Service Bus Queue or Topic Subscription diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py index b5a25f44cf4..ac3f3b86470 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py @@ -129,7 +129,7 @@ async def defer(self) -> None: # type: ignore await self._settle_message(MESSAGE_DEFER) self._settled = True - async def renew_lock(self) -> datetime.datetime: + async def renew_lock(self, timeout=None) -> datetime.datetime: # pylint: disable=protected-access """Renew the message lock. @@ -140,6 +140,7 @@ async def renew_lock(self) -> datetime.datetime: background task by registering the message with an `azure.servicebus.aio.AutoLockRenew` instance. This operation is only available for non-sessionful messages. + :param float timeout: The total operation timeout in seconds including all the retries. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime :raises: TypeError if the message is sessionful. @@ -157,7 +158,7 @@ async def renew_lock(self) -> datetime.datetime: if not token: raise ValueError("Unable to renew lock - no lock token found.") - expiry = await self._receiver._renew_locks(token) # type: ignore + expiry = await self._receiver._renew_locks(token, timeout=timeout) # type: ignore self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime return self._expiry diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py index d6fb5d3722a..d4cdfce0bed 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py @@ -5,7 +5,8 @@ import logging import asyncio import uuid -from typing import TYPE_CHECKING, Any +import time +from typing import TYPE_CHECKING, Any, Callable, Optional import uamqp from uamqp.message import MessageProperties @@ -124,13 +125,13 @@ async def _backoff( self, retried_times, last_exception, - timeout=None, + abs_timeout_time=None, entity_name=None ): entity_name = entity_name or self._container_id backoff = self._config.retry_backoff_factor * 2 ** retried_times if backoff <= self._config.retry_backoff_max and ( - timeout is None or backoff <= timeout + abs_timeout_time is None or (backoff + time.time()) <= abs_timeout_time ): await asyncio.sleep(backoff) _LOGGER.info( @@ -147,30 +148,34 @@ async def _backoff( raise last_exception async def _do_retryable_operation(self, operation, timeout=None, **kwargs): + # type: (Callable, Optional[float], Any) -> Any require_last_exception = kwargs.pop("require_last_exception", False) require_timeout = kwargs.pop("require_timeout", False) retried_times = 0 last_exception = None max_retries = self._config.retry_total + abs_timeout_time = (time.time() + timeout) if (require_timeout and timeout) else None + while retried_times <= max_retries: try: - if require_last_exception: - kwargs["last_exception"] = last_exception if require_timeout: - kwargs["timeout"] = timeout + remaining_timeout = abs_timeout_time - time.time() + kwargs["timeout"] = remaining_timeout return await operation(**kwargs) except StopAsyncIteration: raise except Exception as exception: # pylint: disable=broad-except last_exception = await self._handle_exception(exception) + if require_last_exception: + kwargs["last_exception"] = last_exception retried_times += 1 if retried_times > max_retries: break await self._backoff( retried_times=retried_times, last_exception=last_exception, - timeout=timeout + abs_timeout_time=abs_timeout_time ) _LOGGER.info( @@ -211,12 +216,14 @@ async def _mgmt_request_response( except Exception as exp: # pylint: disable=broad-except raise ServiceBusError("Management request failed: {}".format(exp), exp) - async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, **kwargs): + async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=5, **kwargs): return await self._do_retryable_operation( self._mgmt_request_response, mgmt_operation=mgmt_operation, message=message, callback=callback, + timeout=timeout, + require_timeout=True, **kwargs ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py index 83134591c84..fee1dde97a3 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_client_async.py @@ -137,11 +137,11 @@ def from_connection_string( if token and token_expiry: credential = ServiceBusSASTokenCredential(token, token_expiry) elif policy and key: - credential = ServiceBusSharedKeyCredential(policy, key) # type: ignore + credential = ServiceBusSharedKeyCredential(policy, key) # type: ignore return cls( fully_qualified_namespace=host, entity_name=entity_in_conn_str or kwargs.pop("entity_name", None), - credential=credential, # type: ignore + credential=credential, # type: ignore **kwargs ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py index c6081c2557f..51a25b04e6f 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py @@ -14,6 +14,7 @@ from uamqp.constants import SenderSettleMode from ._base_handler_async import BaseHandler +from .._common.message import PeekedMessage from ._async_message import ReceivedMessage from .._common.receiver_mixins import ReceiverMixin from .._common.constants import ( @@ -208,13 +209,11 @@ async def _open(self): await self.close() raise - async def close(self): # type: () -> None await super(ServiceBusReceiver, self).close() self._message_iter = None - async def _receive(self, max_message_count=None, timeout=None): # type: (Optional[int], Optional[float]) -> List[ReceivedMessage] # pylint: disable=protected-access @@ -274,12 +273,14 @@ async def _settle_message(self, settlement, lock_tokens, dead_letter_details=Non mgmt_handlers.default ) - async def _renew_locks(self, *lock_tokens): + async def _renew_locks(self, *lock_tokens, timeout=None): + # type: (str, Optional[float]) -> Any message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEWLOCK_OPERATION, message, - mgmt_handlers.lock_renew_op + mgmt_handlers.lock_renew_op, + timeout=timeout ) def get_streaming_message_iter(self, max_wait_time: float = None) -> AsyncIterator[ReceivedMessage]: @@ -409,8 +410,8 @@ async def receive_messages(self, max_message_count=None, max_wait_time=None): require_timeout=True ) - async def receive_deferred_messages(self, sequence_numbers): - # type: (Union[int, List[int]]) -> List[ReceivedMessage] + async def receive_deferred_messages(self, sequence_numbers, timeout=None): + # type: (Union[int, List[int]], Optional[float]) -> List[ReceivedMessage] """Receive messages that have previously been deferred. When receiving deferred messages from a partitioned entity, all of the supplied @@ -418,6 +419,7 @@ async def receive_deferred_messages(self, sequence_numbers): :param Union[int, list[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: list[~azure.servicebus.aio.ReceivedMessage] .. admonition:: Example: @@ -454,11 +456,13 @@ async def receive_deferred_messages(self, sequence_numbers): messages = await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RECEIVE_BY_SEQUENCE_NUMBER, message, - handler + handler, + timeout=timeout ) return messages - async def peek_messages(self, max_message_count=1, sequence_number=0): + async def peek_messages(self, max_message_count=1, sequence_number=0, timeout=None): + # type: (int, int, Optional[float]) -> List[PeekedMessage] """Browse messages currently pending in the queue. Peeked messages are not removed from queue, nor are they locked. They cannot be completed, @@ -467,6 +471,7 @@ async def peek_messages(self, max_message_count=1, sequence_number=0): :param int max_message_count: The maximum number of messages to try and peek. The default value is 1. :param int sequence_number: A message sequence number from which to start browsing messages. + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: list[~azure.servicebus.PeekedMessage] .. admonition:: Example: @@ -498,5 +503,6 @@ async def peek_messages(self, max_message_count=1, sequence_number=0): return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_PEEK_OPERATION, message, - mgmt_handlers.peek_op + mgmt_handlers.peek_op, + timeout=timeout ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py index 082a9371bf6..a19b5993e66 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- import logging import asyncio -from typing import Any, TYPE_CHECKING, Union, List +from typing import Any, TYPE_CHECKING, Union, List, Optional import uamqp from uamqp import SendClientAsync, types @@ -134,14 +134,15 @@ async def _send(self, message, timeout=None, last_exception=None): self._set_msg_timeout(timeout, last_exception) await self._handler.send_message_async(message.message) - async def schedule_messages(self, messages, schedule_time_utc): - # type: (Union[Message, List[Message]], datetime.datetime) -> List[int] + async def schedule_messages(self, messages, schedule_time_utc, timeout=None): + # type: (Union[Message, List[Message]], datetime.datetime, Optional[float]) -> List[int] """Send Message or multiple Messages to be enqueued at a specific time by the service. Returns a list of the sequence numbers of the enqueued messages. :param messages: The message or list of messages to schedule. :type messages: ~azure.servicebus.Message or list[~azure.servicebus.Message] :param schedule_time_utc: The utc date and time to enqueue the messages. :type schedule_time_utc: ~datetime.datetime + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: list[int] .. admonition:: Example: @@ -162,16 +163,18 @@ async def schedule_messages(self, messages, schedule_time_utc): return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_SCHEDULE_MESSAGE_OPERATION, request_body, - mgmt_handlers.schedule_op + mgmt_handlers.schedule_op, + timeout=timeout ) - async def cancel_scheduled_messages(self, sequence_numbers): - # type: (Union[int, List[int]]) -> None + async def cancel_scheduled_messages(self, sequence_numbers, timeout=None): + # type: (Union[int, List[int]], Optional[float]) -> None """ Cancel one or more messages that have previously been scheduled and are still pending. :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. @@ -194,7 +197,8 @@ async def cancel_scheduled_messages(self, sequence_numbers): return await self._mgmt_request_response_with_retry( REQUEST_RESPONSE_CANCEL_SCHEDULED_MESSAGE_OPERATION, request_body, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) @classmethod @@ -237,8 +241,8 @@ def from_connection_string( ) return cls(**constructor_args) - async def send_messages(self, message): - # type: (Union[Message, BatchMessage, List[Message]]) -> None + async def send_messages(self, message, timeout=None): + # type: (Union[Message, BatchMessage, List[Message]], Optional[float]) -> None """Sends message and blocks until acknowledgement is received or operation times out. If a list of messages was provided, attempts to send them as a single batch, throwing a @@ -246,6 +250,7 @@ async def send_messages(self, message): :param message: The ServiceBus message to be sent. :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage or list[~azure.servicebus.Message] + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: None :raises: :class: ~azure.servicebus.exceptions.OperationTimeoutError if sending times out. @@ -281,6 +286,7 @@ async def send_messages(self, message): await self._do_retryable_operation( self._send, message=message, + timeout=timeout, require_timeout=True, require_last_exception=True ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py index 61a0f303572..7e477749556 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- import logging import datetime -from typing import Union +from typing import Union, Optional import six from .._servicebus_session import BaseSession @@ -40,12 +40,13 @@ class ServiceBusSession(BaseSession): :caption: Get session from a receiver """ - async def get_state(self): - # type: () -> str + async def get_state(self, timeout=None): + # type: (Optional[float]) -> str """Get the session state. Returns None if no state has been set. + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: str .. admonition:: Example: @@ -61,19 +62,21 @@ async def get_state(self): response = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id}, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) session_state = response.get(MGMT_RESPONSE_SESSION_STATE) if isinstance(session_state, six.binary_type): session_state = session_state.decode('UTF-8') return session_state - async def set_state(self, state): - # type: (Union[str, bytes, bytearray]) -> None + async def set_state(self, state, timeout=None): + # type: (Union[str, bytes, bytearray], Optional[float]) -> None """Set the session state. :param state: The state value. :type state: Union[str, bytes, bytearray] + :param float timeout: The total operation timeout in seconds including all the retries. :rtype: None .. admonition:: Example: @@ -90,11 +93,12 @@ async def set_state(self, state): return await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id, MGMT_REQUEST_SESSION_STATE: bytearray(state)}, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) - async def renew_lock(self): - # type: () -> datetime.datetime + async def renew_lock(self, timeout=None): + # type: (Optional[float]) -> datetime.datetime """Renew the session lock. This operation must be performed periodically in order to retain a lock on the @@ -105,6 +109,7 @@ async def renew_lock(self): This operation can also be performed as a threaded background task by registering the session with an `azure.servicebus.aio.AutoLockRenew` instance. + :param float timeout: The total operation timeout in seconds including all the retries. :returns: The utc datetime the lock is set to expire at. :rtype: datetime @@ -121,7 +126,8 @@ async def renew_lock(self): expiry = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id}, - mgmt_handlers.default + mgmt_handlers.default, + timeout=timeout ) expiry_timestamp = expiry[MGMT_RESPONSE_RECEIVER_EXPIRATION]/1000.0 self._locked_until_utc = utc_from_timestamp(expiry_timestamp) # type: datetime.datetime From 7e83bb382e9ae2bf11e63724236e547aa2b5578e Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Thu, 17 Sep 2020 17:30:18 -0700 Subject: [PATCH 03/18] small fix --- .../azure-servicebus/azure/servicebus/_base_handler.py | 10 +--------- .../azure/servicebus/_common/_configuration.py | 3 +-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index af3c7e3203d..ed65c38debe 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -332,16 +332,8 @@ def _mgmt_request_response( except Exception as exp: # pylint: disable=broad-except raise ServiceBusError("Management request failed: {}".format(exp), exp) - def _mgmt_request_response_with_retry( - self, - mgmt_operation, - message, - callback, - timeout=5, - **kwargs - ): + def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=5, **kwargs): # type: (bytes, Dict[str, Any], Callable, float, Any) -> Any - timeout = timeout or self._config.timeout return self._do_retryable_operation( self._mgmt_request_response, mgmt_operation=mgmt_operation, diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py index 8005ae5c24e..05a9c960aac 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/_configuration.py @@ -13,7 +13,6 @@ def __init__(self, **kwargs): self.retry_total = kwargs.get("retry_total", 3) # type: int self.retry_backoff_factor = kwargs.get("retry_backoff_factor", 0.8) # type: float self.retry_backoff_max = kwargs.get("retry_backoff_max", 120) # type: int - self.timeout = kwargs.get("timeout", 60) # type: float self.logging_enable = kwargs.get("logging_enable", False) # type: bool self.http_proxy = kwargs.get("http_proxy") # type: Optional[Dict[str, Any]] self.transport_type = ( @@ -26,4 +25,4 @@ def __init__(self, **kwargs): self.encoding = kwargs.get("encoding", "UTF-8") self.auto_reconnect = kwargs.get("auto_reconnect", True) self.keep_alive = kwargs.get("keep_alive", 30) - self.timeout = kwargs.get("timeout", None) + self.timeout = kwargs.get("timeout", 60) # type: float From 1275da10dfeab0ff3bafff4568bd1e90846f729a Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Fri, 18 Sep 2020 13:28:08 -0700 Subject: [PATCH 04/18] fix 2.7 syntax error --- .../azure/servicebus/_servicebus_receiver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py index 2b0e51eaf07..e2c8d2ee3aa 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -271,8 +271,9 @@ def _settle_message(self, settlement, lock_tokens, dead_letter_details=None): mgmt_handlers.default ) - def _renew_locks(self, *lock_tokens, timeout=None): - # type: (str, Optional[float]) -> Any + def _renew_locks(self, *lock_tokens, **kwargs): + # type: (str, Any) -> Any + timeout = kwargs.pop("timeout", None) message = {MGMT_REQUEST_LOCK_TOKENS: types.AMQPArray(lock_tokens)} return self._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEWLOCK_OPERATION, From 6c282b408de0abacdd1503636f97281ae39242b4 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Fri, 18 Sep 2020 14:28:36 -0700 Subject: [PATCH 05/18] fix mypy --- .../azure/servicebus/_base_handler.py | 8 ++--- .../servicebus/aio/_base_handler_async.py | 32 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index ed65c38debe..ba2ff4a645d 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -297,10 +297,10 @@ def _mgmt_request_response( message, callback, keep_alive_associated_link=True, - timeout=5, + timeout=None, **kwargs ): - # type: (bytes, uamqp.Message, Callable, bool, float, Any) -> uamqp.Message + # type: (bytes, uamqp.Message, Callable, bool, Optional[float], Any) -> uamqp.Message self._open() application_properties = {} @@ -332,8 +332,8 @@ def _mgmt_request_response( except Exception as exp: # pylint: disable=broad-except raise ServiceBusError("Management request failed: {}".format(exp), exp) - def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=5, **kwargs): - # type: (bytes, Dict[str, Any], Callable, float, Any) -> Any + def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=None, **kwargs): + # type: (bytes, Dict[str, Any], Callable, Optional[float], Any) -> Any return self._do_retryable_operation( self._mgmt_request_response, mgmt_operation=mgmt_operation, diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py index d4cdfce0bed..3e1c7c41006 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py @@ -6,7 +6,7 @@ import asyncio import uuid import time -from typing import TYPE_CHECKING, Any, Callable, Optional +from typing import TYPE_CHECKING, Any, Callable, Optional, Dict import uamqp from uamqp.message import MessageProperties @@ -159,7 +159,7 @@ async def _do_retryable_operation(self, operation, timeout=None, **kwargs): while retried_times <= max_retries: try: - if require_timeout: + if require_timeout and abs_timeout_time: remaining_timeout = abs_timeout_time - time.time() kwargs["timeout"] = remaining_timeout return await operation(**kwargs) @@ -171,23 +171,28 @@ async def _do_retryable_operation(self, operation, timeout=None, **kwargs): kwargs["last_exception"] = last_exception retried_times += 1 if retried_times > max_retries: - break + _LOGGER.info( + "%r operation has exhausted retry. Last exception: %r.", + self._container_id, + last_exception, + ) + raise last_exception await self._backoff( retried_times=retried_times, last_exception=last_exception, abs_timeout_time=abs_timeout_time ) - _LOGGER.info( - "%r operation has exhausted retry. Last exception: %r.", - self._container_id, - last_exception, - ) - raise last_exception - async def _mgmt_request_response( - self, mgmt_operation, message, callback, keep_alive_associated_link=True, **kwargs + self, + mgmt_operation, + message, + callback, + keep_alive_associated_link=True, + timeout=None, + **kwargs ): + # type: (bytes, uamqp.Message, Callable, bool, Optional[float], Any) -> uamqp.Message await self._open() application_properties = {} @@ -211,12 +216,13 @@ async def _mgmt_request_response( mgmt_operation, op_type=MGMT_REQUEST_OP_TYPE_ENTITY_MGMT, node=self._mgmt_target.encode(self._config.encoding), - timeout=5000, + timeout=timeout, callback=callback) except Exception as exp: # pylint: disable=broad-except raise ServiceBusError("Management request failed: {}".format(exp), exp) - async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=5, **kwargs): + async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=None, **kwargs): + # type: (bytes, Dict[str, Any], Callable, Optional[float], Any) -> Any return await self._do_retryable_operation( self._mgmt_request_response, mgmt_operation=mgmt_operation, From dfcd17cdf2460999b897ff48eebe514ab71e25b6 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Fri, 18 Sep 2020 15:02:14 -0700 Subject: [PATCH 06/18] fix mypy --- .../azure-servicebus/azure/servicebus/_base_handler.py | 2 +- .../azure/servicebus/_servicebus_session.py | 8 ++++---- .../azure/servicebus/aio/_base_handler_async.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index ba2ff4a645d..400f5f0731d 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -326,7 +326,7 @@ def _mgmt_request_response( mgmt_operation, op_type=MGMT_REQUEST_OP_TYPE_ENTITY_MGMT, node=self._mgmt_target.encode(self._config.encoding), - timeout=timeout * 1000, + timeout=timeout * 1000 if timeout else None, callback=callback ) except Exception as exp: # pylint: disable=broad-except diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py index a837a2238d8..03c220d5307 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py @@ -117,7 +117,7 @@ def get_state(self, **kwargs): mgmt_handlers.default, timeout=timeout ) - session_state = response.get(MGMT_RESPONSE_SESSION_STATE) + session_state = response.get(MGMT_RESPONSE_SESSION_STATE) # type: ignore if isinstance(session_state, six.binary_type): session_state = session_state.decode(self._encoding) return session_state @@ -143,7 +143,7 @@ def set_state(self, state, **kwargs): self._check_live() timeout = kwargs.pop("timeout", None) state = state.encode(self._encoding) if isinstance(state, six.text_type) else state - return self._receiver._mgmt_request_response_with_retry( + return self._receiver._mgmt_request_response_with_retry( # type: ignore REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._id, MGMT_REQUEST_SESSION_STATE: bytearray(state)}, mgmt_handlers.default, @@ -184,7 +184,7 @@ def renew_lock(self, **kwargs): mgmt_handlers.default, timeout=timeout ) - expiry_timestamp = expiry[MGMT_RESPONSE_RECEIVER_EXPIRATION]/1000.0 - self._locked_until_utc = utc_from_timestamp(expiry_timestamp) # type: datetime.datetime + expiry_timestamp = expiry[MGMT_RESPONSE_RECEIVER_EXPIRATION]/1000.0 # type: ignore + self._locked_until_utc = utc_from_timestamp(expiry_timestamp) # type: datetime.datetime return self._locked_until_utc diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py index 3e1c7c41006..2fad46f4abe 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py @@ -216,7 +216,7 @@ async def _mgmt_request_response( mgmt_operation, op_type=MGMT_REQUEST_OP_TYPE_ENTITY_MGMT, node=self._mgmt_target.encode(self._config.encoding), - timeout=timeout, + timeout=timeout * 1000 if timeout else None, callback=callback) except Exception as exp: # pylint: disable=broad-except raise ServiceBusError("Management request failed: {}".format(exp), exp) From 8411791248b71411330539198341ae722851ff52 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Mon, 21 Sep 2020 11:58:27 -0700 Subject: [PATCH 07/18] add test and fix a small bug --- .../azure/servicebus/_servicebus_sender.py | 4 ++- .../tests/async_tests/test_queues_async.py | 27 +++++++++--------- .../tests/async_tests/test_sessions_async.py | 6 ++-- .../azure-servicebus/tests/test_queues.py | 28 ++++++++++--------- .../azure-servicebus/tests/test_sessions.py | 10 +++---- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index 43735cd6c91..a764e200f9b 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -48,7 +48,9 @@ def _create_attribute(self): self.entity_name = self._entity_name def _set_msg_timeout(self, timeout=None, last_exception=None): + # pylint: disable=protected-access if not timeout: + self._handler._msg_timeout = 0 return timeout_time = time.time() + timeout remaining_time = timeout_time - time.time() @@ -59,7 +61,7 @@ def _set_msg_timeout(self, timeout=None, last_exception=None): error = OperationTimeoutError("Send operation timed out") _LOGGER.info("%r send operation timed out. (%r)", self._name, error) raise error - self._handler._msg_timeout = remaining_time * 1000 # type: ignore # pylint: disable=protected-access + self._handler._msg_timeout = remaining_time * 1000 # type: ignore @classmethod def _build_schedule_request(cls, schedule_time_utc, *messages): diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index 2fcb31fe61f..ef9deb76e97 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -52,7 +52,8 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel async with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(10): message = Message("Handler message no. {}".format(i)) - await sender.send_messages(message) + await sender.send_messages(message, timeout=5) + assert int(sender._handler._msg_timeout) == 5 * 1000 with pytest.raises(ServiceBusConnectionError): await (sb_client.get_queue_session_receiver(servicebus_queue.name, session_id="test", max_wait_time=5))._open_with_retry() @@ -66,7 +67,6 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel assert count == 10 - @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @@ -81,6 +81,7 @@ async def test_async_queue_by_queue_client_send_multiple_messages(self, serviceb message = Message("Handler message no. {}".format(i)) messages.append(message) await sender.send_messages(messages) + assert sender._handler._msg_timeout == 0 async with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) as receiver: count = 0 @@ -91,7 +92,6 @@ async def test_async_queue_by_queue_client_send_multiple_messages(self, serviceb assert count == 10 - @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer() @@ -133,7 +133,6 @@ async def test_github_issue_6178_async(self, servicebus_namespace_connection_str await message.complete() await asyncio.sleep(40) - @pytest.mark.liveTest @pytest.mark.live_test_only @CachedResourceGroupPreparer(name_prefix='servicebustest') @@ -327,7 +326,7 @@ async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_defe assert count == 10 - deferred = await receiver.receive_deferred_messages(deferred_messages) + deferred = await receiver.receive_deferred_messages(deferred_messages, timeout=5) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -360,7 +359,7 @@ async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_defe assert count == 10 async with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages) + deferred = await session.receive_deferred_messages(deferred_messages, timeout=0) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -395,7 +394,7 @@ async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_defe assert count == 10 async with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages) + deferred = await session.receive_deferred_messages(deferred_messages, timeout=None) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -628,7 +627,7 @@ async def test_async_queue_by_servicebus_client_browse_messages_with_receiver(se message = Message("Test message no. {}".format(i)) await sender.send_messages(message) - messages = await receiver.peek_messages(5) + messages = await receiver.peek_messages(5, timeout=5) assert len(messages) > 0 assert all(isinstance(m, PeekedMessage) for m in messages) for message in messages: @@ -646,7 +645,7 @@ async def test_async_queue_by_servicebus_client_browse_empty_messages(self, serv servicebus_namespace_connection_string, logging_enable=False) as sb_client: async with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5, receive_mode=ReceiveMode.PeekLock, prefetch_count=10) as receiver: - messages = await receiver.peek_messages(10) + messages = await receiver.peek_messages(10, timeout=0) assert len(messages) == 0 @pytest.mark.liveTest @@ -883,9 +882,9 @@ async def test_async_queue_message_lock_renew(self, servicebus_namespace_connect messages = await receiver.receive_messages(max_wait_time=10) assert len(messages) == 1 time.sleep(15) - await messages[0].renew_lock() + await messages[0].renew_lock(timeout=5) time.sleep(15) - await messages[0].renew_lock() + await messages[0].renew_lock(timeout=0) time.sleep(15) assert not messages[0]._lock_expired await messages[0].complete() @@ -1019,7 +1018,7 @@ async def test_async_queue_schedule_multiple_messages(self, servicebus_namespace received_messages.append(message) await message.complete() - tokens = await sender.schedule_messages(received_messages, scheduled_enqueue_time) + tokens = await sender.schedule_messages(received_messages, scheduled_enqueue_time, timeout=5) assert len(tokens) == 2 messages = await receiver.receive_messages(max_wait_time=120) @@ -1053,10 +1052,10 @@ async def test_async_queue_cancel_scheduled_messages(self, servicebus_namespace_ async with sb_client.get_queue_sender(servicebus_queue.name) as sender: message_a = Message("Test scheduled message") message_b = Message("Test scheduled message") - tokens = await sender.schedule_messages([message_a, message_b], enqueue_time) + tokens = await sender.schedule_messages([message_a, message_b], enqueue_time, timeout=0) assert len(tokens) == 2 - await sender.cancel_scheduled_messages(tokens) + await sender.cancel_scheduled_messages(tokens, timeout=None) messages = await receiver.receive_messages(max_wait_time=120) assert len(messages) == 0 diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py index 616561373b2..cd751fceaf8 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_sessions_async.py @@ -467,7 +467,7 @@ async def test_async_session_by_servicebus_client_renew_client_locks(self, servi assert m.lock_token is not None time.sleep(5) initial_expiry = receiver.session.locked_until_utc - await receiver.session.renew_lock() + await receiver.session.renew_lock(timeout=5) assert (receiver.session.locked_until_utc - initial_expiry) >= timedelta(seconds=5) finally: await messages[0].complete() @@ -733,8 +733,8 @@ async def test_async_session_get_set_state_with_receiver(self, servicebus_namesp await sender.send_messages(message) async with sb_client.get_queue_session_receiver(servicebus_queue.name, session_id=session_id, max_wait_time=5) as session: - assert await session.session.get_state() == None - await session.session.set_state("first_state") + assert await session.session.get_state(timeout=5) == None + await session.session.set_state("first_state", timeout=5) count = 0 async for m in session: assert m.session_id == session_id diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index 452490f0af4..ecd36f6c6d2 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -61,7 +61,8 @@ def test_receive_and_delete_reconnect_interaction(self, servicebus_namespace_con with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(5): - sender.send_messages(Message("Message {}".format(i))) + sender.send_messages(Message("Message {}".format(i)), timeout=5) + assert int(sender._handler._msg_timeout) == 5 * 1000 with sb_client.get_queue_receiver(servicebus_queue.name, receive_mode=ReceiveMode.ReceiveAndDelete, @@ -119,6 +120,7 @@ def test_queue_by_queue_client_conn_str_receive_handler_peeklock(self, servicebu message.to = 'to' message.reply_to = 'reply_to' sender.send_messages(message) + assert sender._handler._msg_timeout == 0 receiver = sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) count = 0 @@ -422,7 +424,7 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_client( message.defer() assert count == 10 - deferred = receiver.receive_deferred_messages(deferred_messages) + deferred = receiver.receive_deferred_messages(deferred_messages, timeout=5) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -463,7 +465,7 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receive with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5, receive_mode=ReceiveMode.PeekLock) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages) + deferred = receiver.receive_deferred_messages(deferred_messages, timeout=0) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -503,7 +505,7 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receive with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages) + deferred = receiver.receive_deferred_messages(deferred_messages, timeout=None) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -732,7 +734,7 @@ def test_queue_by_servicebus_client_browse_messages_client(self, servicebus_name sender.send_messages(message) with sb_client.get_queue_receiver(servicebus_queue.name) as receiver: - messages = receiver.peek_messages(5) + messages = receiver.peek_messages(5, timeout=5) assert len(messages) == 5 assert all(isinstance(m, PeekedMessage) for m in messages) for message in messages: @@ -772,7 +774,7 @@ def test_queue_by_servicebus_client_browse_messages_with_receiver(self, serviceb ) sender.send_messages(message) - messages = receiver.peek_messages(5) + messages = receiver.peek_messages(5, timeout=0) assert len(messages) > 0 assert all(isinstance(m, PeekedMessage) for m in messages) for message in messages: @@ -824,7 +826,7 @@ def test_queue_by_servicebus_client_browse_empty_messages(self, servicebus_names max_wait_time=5, receive_mode=ReceiveMode.PeekLock, prefetch_count=10) as receiver: - messages = receiver.peek_messages(10) + messages = receiver.peek_messages(10, timeout=None) assert len(messages) == 0 @@ -880,7 +882,7 @@ def test_queue_by_servicebus_client_renew_message_locks(self, servicebus_namespa assert not m._lock_expired time.sleep(5) initial_expiry = m.locked_until_utc - m.renew_lock() + m.renew_lock(timeout=5) assert (m.locked_until_utc - initial_expiry) >= timedelta(seconds=5) finally: messages[0].complete() @@ -1081,9 +1083,9 @@ def test_queue_message_lock_renew(self, servicebus_namespace_connection_string, messages = receiver.receive_messages(max_wait_time=10) assert len(messages) == 1 time.sleep(15) - messages[0].renew_lock() + messages[0].renew_lock(timeout=0) time.sleep(15) - messages[0].renew_lock() + messages[0].renew_lock(timeout=0) time.sleep(15) assert not messages[0]._lock_expired messages[0].complete() @@ -1270,7 +1272,7 @@ def test_queue_schedule_multiple_messages(self, servicebus_namespace_connection_ received_messages.append(message) message.complete() - tokens = sender.schedule_messages(received_messages, scheduled_enqueue_time) + tokens = sender.schedule_messages(received_messages, scheduled_enqueue_time, timeout=5) assert len(tokens) == 2 messages = receiver.receive_messages(max_wait_time=120) @@ -1318,10 +1320,10 @@ def test_queue_cancel_scheduled_messages(self, servicebus_namespace_connection_s with sb_client.get_queue_sender(servicebus_queue.name) as sender: message_a = Message("Test scheduled message") message_b = Message("Test scheduled message") - tokens = sender.schedule_messages([message_a, message_b], enqueue_time) + tokens = sender.schedule_messages([message_a, message_b], enqueue_time, timeout=0) assert len(tokens) == 2 - sender.cancel_scheduled_messages(tokens) + sender.cancel_scheduled_messages(tokens, timeout=None) messages = receiver.receive_messages(max_wait_time=120) try: diff --git a/sdk/servicebus/azure-servicebus/tests/test_sessions.py b/sdk/servicebus/azure-servicebus/tests/test_sessions.py index bf065a565bd..f958d1229cf 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_sessions.py +++ b/sdk/servicebus/azure-servicebus/tests/test_sessions.py @@ -555,7 +555,7 @@ def test_session_by_servicebus_client_renew_client_locks(self, servicebus_namesp assert m.lock_token is not None time.sleep(5) initial_expiry = receiver.session._locked_until_utc - receiver.session.renew_lock() + receiver.session.renew_lock(timeout=5) assert (receiver.session._locked_until_utc - initial_expiry) >= timedelta(seconds=5) finally: messages[0].complete() @@ -730,7 +730,7 @@ def test_session_schedule_message(self, servicebus_namespace_connection_string, count = 0 while not messages and count < 12: messages = receiver.receive_messages(max_wait_time=10) - receiver.session.renew_lock() + receiver.session.renew_lock(timeout=0) count += 1 data = str(messages[0]) @@ -769,7 +769,7 @@ def test_session_schedule_multiple_messages(self, servicebus_namespace_connectio messages = [] count = 0 while len(messages) < 2 and count < 12: - receiver.session.renew_lock() + receiver.session.renew_lock(timeout=0) messages = receiver.receive_messages(max_wait_time=15) time.sleep(5) count += 1 @@ -829,8 +829,8 @@ def test_session_get_set_state_with_receiver(self, servicebus_namespace_connecti sender.send_messages(message) with sb_client.get_queue_session_receiver(servicebus_queue.name, session_id=session_id, max_wait_time=5) as session: - assert session.session.get_state() == None - session.session.set_state("first_state") + assert session.session.get_state(timeout=5) == None + session.session.set_state("first_state", timeout=5) count = 0 for m in session: assert m.session_id == session_id From 4161d6889a1091ad565965dea5f5f18a5eaa419e Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Mon, 21 Sep 2020 15:53:53 -0700 Subject: [PATCH 08/18] improve code --- .../azure-servicebus/azure/servicebus/_servicebus_sender.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index a764e200f9b..b5d79bccd83 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -52,16 +52,14 @@ def _set_msg_timeout(self, timeout=None, last_exception=None): if not timeout: self._handler._msg_timeout = 0 return - timeout_time = time.time() + timeout - remaining_time = timeout_time - time.time() - if remaining_time <= 0.0: + if timeout <= 0.0: if last_exception: error = last_exception else: error = OperationTimeoutError("Send operation timed out") _LOGGER.info("%r send operation timed out. (%r)", self._name, error) raise error - self._handler._msg_timeout = remaining_time * 1000 # type: ignore + self._handler._msg_timeout = timeout * 1000 # type: ignore @classmethod def _build_schedule_request(cls, schedule_time_utc, *messages): From b46131ae0530fc9b946490aca7c3cec2c3c47ce0 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Mon, 21 Sep 2020 19:58:19 -0700 Subject: [PATCH 09/18] improve test code --- .../azure-servicebus/tests/async_tests/test_queues_async.py | 2 +- sdk/servicebus/azure-servicebus/tests/test_queues.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index ef9deb76e97..42ad5035980 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -53,7 +53,7 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel for i in range(10): message = Message("Handler message no. {}".format(i)) await sender.send_messages(message, timeout=5) - assert int(sender._handler._msg_timeout) == 5 * 1000 + assert 4990 <= sender._handler._msg_timeout <= 5000 # initial retry logic consumes cycles, usually just 1ms with pytest.raises(ServiceBusConnectionError): await (sb_client.get_queue_session_receiver(servicebus_queue.name, session_id="test", max_wait_time=5))._open_with_retry() diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index ecd36f6c6d2..ee025578106 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -62,7 +62,7 @@ def test_receive_and_delete_reconnect_interaction(self, servicebus_namespace_con with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(5): sender.send_messages(Message("Message {}".format(i)), timeout=5) - assert int(sender._handler._msg_timeout) == 5 * 1000 + assert 4990 <= sender._handler._msg_timeout <= 5000 # initial retry logic consumes some cycles, usually just 1ms with sb_client.get_queue_receiver(servicebus_queue.name, receive_mode=ReceiveMode.ReceiveAndDelete, From 12b2e7ecdff9a5903d523f4c70cb41287261b46e Mon Sep 17 00:00:00 2001 From: "Adam Ling (MSFT)" Date: Mon, 28 Sep 2020 14:40:19 -0700 Subject: [PATCH 10/18] Update sdk/servicebus/azure-servicebus/CHANGELOG.md Co-authored-by: KieranBrantnerMagee --- sdk/servicebus/azure-servicebus/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index a5f905f1517..bc616a4a39d 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -4,7 +4,7 @@ **New Features** -* Added support for timeout on operations: +* Added support for `timeout` parameter on the following operations: - `ServiceBusSender`: `send_messages`, `schedule_messages` and `cancel_scheduled_messages` - `ServiceBusReceiver`: `receive_deferred_messages` and `peek_messages` - `ServiceBusSession`: `get_state`, `set_state` and `renew_lock` From d0291f790d4bc23611cb3874b6d9c1269915eead Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Mon, 28 Sep 2020 17:10:10 -0700 Subject: [PATCH 11/18] move timeout into kwargs in async --- .../azure/servicebus/aio/_async_message.py | 7 +++--- .../aio/_servicebus_receiver_async.py | 17 ++++++++------ .../aio/_servicebus_sender_async.py | 21 +++++++++-------- .../aio/_servicebus_session_async.py | 23 +++++++++++-------- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py index ac3f3b86470..3fd53fdf5cb 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py @@ -5,7 +5,7 @@ # ------------------------------------------------------------------------- import logging import datetime -from typing import Optional +from typing import Any, Optional from .._common import message as sync_message from .._common.constants import ( @@ -129,7 +129,7 @@ async def defer(self) -> None: # type: ignore await self._settle_message(MESSAGE_DEFER) self._settled = True - async def renew_lock(self, timeout=None) -> datetime.datetime: + async def renew_lock(self, **kwargs: Any) -> datetime.datetime: # pylint: disable=protected-access """Renew the message lock. @@ -140,7 +140,7 @@ async def renew_lock(self, timeout=None) -> datetime.datetime: background task by registering the message with an `azure.servicebus.aio.AutoLockRenew` instance. This operation is only available for non-sessionful messages. - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime :raises: TypeError if the message is sessionful. @@ -148,6 +148,7 @@ async def renew_lock(self, timeout=None) -> datetime.datetime: :raises: ~azure.servicebus.exceptions.SessionLockExpired if session lock has already expired. :raises: ~azure.servicebus.exceptions.MessageAlreadySettled is message has already been settled. """ + timeout = kwargs.pop("timeout", None) try: if self._receiver.session: # type: ignore raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py index 51a25b04e6f..2115b73615b 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py @@ -410,8 +410,8 @@ async def receive_messages(self, max_message_count=None, max_wait_time=None): require_timeout=True ) - async def receive_deferred_messages(self, sequence_numbers, timeout=None): - # type: (Union[int, List[int]], Optional[float]) -> List[ReceivedMessage] + async def receive_deferred_messages(self, sequence_numbers, **kwargs): + # type: (Union[int, List[int]], Any) -> List[ReceivedMessage] """Receive messages that have previously been deferred. When receiving deferred messages from a partitioned entity, all of the supplied @@ -419,7 +419,7 @@ async def receive_deferred_messages(self, sequence_numbers, timeout=None): :param Union[int, list[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: list[~azure.servicebus.aio.ReceivedMessage] .. admonition:: Example: @@ -432,6 +432,7 @@ async def receive_deferred_messages(self, sequence_numbers, timeout=None): :caption: Receive deferred messages from ServiceBus. """ + timeout = kwargs.pop("timeout", None) self._check_live() if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] @@ -461,8 +462,8 @@ async def receive_deferred_messages(self, sequence_numbers, timeout=None): ) return messages - async def peek_messages(self, max_message_count=1, sequence_number=0, timeout=None): - # type: (int, int, Optional[float]) -> List[PeekedMessage] + async def peek_messages(self, max_message_count=1, **kwargs): + # type: (int, Optional[float]) -> List[PeekedMessage] """Browse messages currently pending in the queue. Peeked messages are not removed from queue, nor are they locked. They cannot be completed, @@ -470,8 +471,8 @@ async def peek_messages(self, max_message_count=1, sequence_number=0, timeout=No :param int max_message_count: The maximum number of messages to try and peek. The default value is 1. - :param int sequence_number: A message sequence number from which to start browsing messages. - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword int sequence_number: A message sequence number from which to start browsing messages. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: list[~azure.servicebus.PeekedMessage] .. admonition:: Example: @@ -483,6 +484,8 @@ async def peek_messages(self, max_message_count=1, sequence_number=0, timeout=No :dedent: 4 :caption: Peek messages in the queue. """ + sequence_number = kwargs.pop("sequence_number", 0) + timeout = kwargs.pop("timeout", None) self._check_live() if not sequence_number: sequence_number = self._last_received_sequenced_number or 1 diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py index a19b5993e66..8060017f4e5 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py @@ -134,15 +134,15 @@ async def _send(self, message, timeout=None, last_exception=None): self._set_msg_timeout(timeout, last_exception) await self._handler.send_message_async(message.message) - async def schedule_messages(self, messages, schedule_time_utc, timeout=None): - # type: (Union[Message, List[Message]], datetime.datetime, Optional[float]) -> List[int] + async def schedule_messages(self, messages, schedule_time_utc, **kwargs): + # type: (Union[Message, List[Message]], datetime.datetime, Any) -> List[int] """Send Message or multiple Messages to be enqueued at a specific time by the service. Returns a list of the sequence numbers of the enqueued messages. :param messages: The message or list of messages to schedule. :type messages: ~azure.servicebus.Message or list[~azure.servicebus.Message] :param schedule_time_utc: The utc date and time to enqueue the messages. :type schedule_time_utc: ~datetime.datetime - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: list[int] .. admonition:: Example: @@ -155,6 +155,7 @@ async def schedule_messages(self, messages, schedule_time_utc, timeout=None): :caption: Schedule a message to be sent in future """ # pylint: disable=protected-access + timeout = kwargs.pop("timeout", None) await self._open() if isinstance(messages, Message): request_body = self._build_schedule_request(schedule_time_utc, messages) @@ -167,14 +168,14 @@ async def schedule_messages(self, messages, schedule_time_utc, timeout=None): timeout=timeout ) - async def cancel_scheduled_messages(self, sequence_numbers, timeout=None): - # type: (Union[int, List[int]], Optional[float]) -> None + async def cancel_scheduled_messages(self, sequence_numbers, **kwargs): + # type: (Union[int, List[int]], Any) -> None """ Cancel one or more messages that have previously been scheduled and are still pending. :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. @@ -188,6 +189,7 @@ async def cancel_scheduled_messages(self, sequence_numbers, timeout=None): :dedent: 4 :caption: Cancelling messages scheduled to be sent in future """ + timeout = kwargs.pop("timeout", None) await self._open() if isinstance(sequence_numbers, int): numbers = [types.AMQPLong(sequence_numbers)] @@ -241,8 +243,8 @@ def from_connection_string( ) return cls(**constructor_args) - async def send_messages(self, message, timeout=None): - # type: (Union[Message, BatchMessage, List[Message]], Optional[float]) -> None + async def send_messages(self, message, **kwargs): + # type: (Union[Message, BatchMessage, List[Message]], Any) -> None """Sends message and blocks until acknowledgement is received or operation times out. If a list of messages was provided, attempts to send them as a single batch, throwing a @@ -250,7 +252,7 @@ async def send_messages(self, message, timeout=None): :param message: The ServiceBus message to be sent. :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage or list[~azure.servicebus.Message] - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: None :raises: :class: ~azure.servicebus.exceptions.OperationTimeoutError if sending times out. @@ -271,6 +273,7 @@ async def send_messages(self, message, timeout=None): :caption: Send message. """ + timeout = kwargs.pop("timeout", None) message = transform_messages_to_sendable_if_needed(message) try: batch = await self.create_batch() diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py index d37aff3c449..8c6cd8db308 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py @@ -4,7 +4,7 @@ # -------------------------------------------------------------------------------------------- import logging import datetime -from typing import Union, Optional +from typing import Union, Any import six from .._servicebus_session import BaseSession @@ -40,13 +40,13 @@ class ServiceBusSession(BaseSession): :caption: Get session from a receiver """ - async def get_state(self, timeout=None): - # type: (Optional[float]) -> str + async def get_state(self, **kwargs): + # type: (Any) -> str """Get the session state. Returns None if no state has been set. - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: str .. admonition:: Example: @@ -58,6 +58,7 @@ async def get_state(self, timeout=None): :dedent: 4 :caption: Get the session state """ + timeout = kwargs.pop("timeout", None) self._check_live() response = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, @@ -70,13 +71,13 @@ async def get_state(self, timeout=None): session_state = session_state.decode('UTF-8') return session_state - async def set_state(self, state, timeout=None): - # type: (Union[str, bytes, bytearray], Optional[float]) -> None + async def set_state(self, state, **kwargs): + # type: (Union[str, bytes, bytearray], Any) -> None """Set the session state. :param state: The state value. :type state: Union[str, bytes, bytearray] - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :rtype: None .. admonition:: Example: @@ -88,6 +89,7 @@ async def set_state(self, state, timeout=None): :dedent: 4 :caption: Set the session state """ + timeout = kwargs.pop("timeout", None) self._check_live() state = state.encode(self._encoding) if isinstance(state, six.text_type) else state return await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access @@ -97,8 +99,8 @@ async def set_state(self, state, timeout=None): timeout=timeout ) - async def renew_lock(self, timeout=None): - # type: (Optional[float]) -> datetime.datetime + async def renew_lock(self, **kwargs): + # type: (Any) -> datetime.datetime """Renew the session lock. This operation must be performed periodically in order to retain a lock on the @@ -109,7 +111,7 @@ async def renew_lock(self, timeout=None): This operation can also be performed as a threaded background task by registering the session with an `azure.servicebus.aio.AutoLockRenew` instance. - :param float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. :returns: The utc datetime the lock is set to expire at. :rtype: datetime @@ -122,6 +124,7 @@ async def renew_lock(self, timeout=None): :dedent: 4 :caption: Renew the session lock before it expires """ + timeout = kwargs.pop("timeout", None) self._check_live() expiry = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, From c03f0e2c2381fb428fce255d20a4455a7f7b2873 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Wed, 30 Sep 2020 13:43:03 -0700 Subject: [PATCH 12/18] addressing pr review and update uamqp dependency to include the latest fix --- sdk/servicebus/azure-servicebus/CHANGELOG.md | 6 ++ .../azure/servicebus/_base_handler.py | 28 +++++-- .../azure/servicebus/_common/message.py | 7 +- .../azure/servicebus/_servicebus_receiver.py | 19 +++-- .../azure/servicebus/_servicebus_sender.py | 25 ++++-- .../azure/servicebus/_servicebus_session.py | 15 +++- .../azure/servicebus/aio/_async_message.py | 12 ++- .../servicebus/aio/_base_handler_async.py | 30 +++++-- .../aio/_servicebus_receiver_async.py | 26 +++--- .../aio/_servicebus_sender_async.py | 29 +++++-- .../aio/_servicebus_session_async.py | 21 +++-- .../azure/servicebus/exceptions.py | 5 +- sdk/servicebus/azure-servicebus/setup.py | 2 +- .../tests/async_tests/test_queues_async.py | 78 ++++++++++++++++-- .../azure-servicebus/tests/test_queues.py | 81 ++++++++++++++++--- shared_requirements.txt | 2 +- 16 files changed, 311 insertions(+), 75 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index a5f905f1517..51a15355944 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -11,8 +11,14 @@ - `ReceivedMessage`: `renew_lock` **Breaking Changes** + * Passing any type other than `ReceiveMode` as parameter `receive_mode` now throws a `TypeError` instead of `AttributeError`. +**BugFixes** + +* Updated uAMQP dependency to 1.2.11. + - Fixed bug where amqp message `footer` and `delivery_annotation` were not encoded into the outgoing payload. + ## 7.0.0b6 (2020-09-10) **New Features** diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py index 400f5f0731d..7bf24816b02 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_base_handler.py @@ -15,7 +15,7 @@ from urllib.parse import quote_plus import uamqp -from uamqp import utils +from uamqp import utils, compat from uamqp.message import MessageProperties from azure.core.credentials import AccessToken @@ -24,6 +24,7 @@ from .exceptions import ( ServiceBusError, ServiceBusAuthenticationError, + OperationTimeoutError, _create_servicebus_exception ) from ._common.utils import create_properties @@ -259,15 +260,15 @@ def _backoff( def _do_retryable_operation(self, operation, timeout=None, **kwargs): # type: (Callable, Optional[float], Any) -> Any require_last_exception = kwargs.pop("require_last_exception", False) - require_timeout = kwargs.pop("require_timeout", False) + operation_requires_timeout = kwargs.pop("operation_requires_timeout", False) retried_times = 0 max_retries = self._config.retry_total - abs_timeout_time = (time.time() + timeout) if (require_timeout and timeout) else None + abs_timeout_time = (time.time() + timeout) if (operation_requires_timeout and timeout) else None while retried_times <= max_retries: try: - if require_timeout and abs_timeout_time: + if operation_requires_timeout and abs_timeout_time: remaining_timeout = abs_timeout_time - time.time() kwargs["timeout"] = remaining_timeout return operation(**kwargs) @@ -301,6 +302,21 @@ def _mgmt_request_response( **kwargs ): # type: (bytes, uamqp.Message, Callable, bool, Optional[float], Any) -> uamqp.Message + """ + Execute an amqp management operation. + + :param bytes mgmt_operation: The type of operation to be performed. This value will + be service-specific, but common values include READ, CREATE and UPDATE. + This value will be added as an application property on the message. + :param message: The message to send in the management request. + :paramtype message: ~uamqp.message.Message + :param callback: The callback which is used to parse the returning message. + :paramtype callback: Callable[int, ~uamqp.message.Message, str] + :param keep_alive_associated_link: A boolean flag for keeping associated amqp sender/receiver link alive when + executing operation on mgmt links. + :param timeout: timeout in seconds executing the mgmt operation. + :rtype: None + """ self._open() application_properties = {} @@ -330,6 +346,8 @@ def _mgmt_request_response( callback=callback ) except Exception as exp: # pylint: disable=broad-except + if isinstance(exp, compat.TimeoutException): + raise OperationTimeoutError("Management operation timed out.", inner_exception=exp) raise ServiceBusError("Management request failed: {}".format(exp), exp) def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=None, **kwargs): @@ -340,7 +358,7 @@ def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, t message=message, callback=callback, timeout=timeout, - require_timeout=True, + operation_requires_timeout=True, **kwargs ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py index 75c80013818..76bd92ad637 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_common/message.py @@ -617,7 +617,6 @@ def _to_outgoing_message(self): via_partition_key=self.via_partition_key ) - @property def dead_letter_error_description(self): # type: () -> Optional[str] @@ -1059,7 +1058,8 @@ def renew_lock(self, **kwargs): Lock renewal can be performed as a background task by registering the message with an `azure.servicebus.AutoLockRenew` instance. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime :raises: TypeError if the message is sessionful. @@ -1077,6 +1077,9 @@ def renew_lock(self, **kwargs): raise ValueError("Unable to renew lock - no lock token found.") timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") + expiry = self._receiver._renew_locks(token, timeout=timeout) # type: ignore self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py index e2c8d2ee3aa..5d7d92cd147 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_receiver.py @@ -415,7 +415,7 @@ def receive_messages(self, max_message_count=None, max_wait_time=None): self._receive, max_message_count=max_message_count, timeout=max_wait_time, - require_timeout=True + operation_requires_timeout=True ) def receive_deferred_messages(self, sequence_numbers, **kwargs): @@ -427,7 +427,8 @@ def receive_deferred_messages(self, sequence_numbers, **kwargs): :param Union[int,List[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: List[~azure.servicebus.ReceivedMessage] .. admonition:: Example: @@ -442,6 +443,8 @@ def receive_deferred_messages(self, sequence_numbers, **kwargs): """ self._check_live() timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] if not sequence_numbers: @@ -467,8 +470,8 @@ def receive_deferred_messages(self, sequence_numbers, **kwargs): ) return messages - def peek_messages(self, max_message_count=1, sequence_number=None, **kwargs): - # type: (int, Optional[int], Any) -> List[PeekedMessage] + def peek_messages(self, max_message_count=1, **kwargs): + # type: (int, Any) -> List[PeekedMessage] """Browse messages currently pending in the queue. Peeked messages are not removed from queue, nor are they locked. They cannot be completed, @@ -476,8 +479,9 @@ def peek_messages(self, max_message_count=1, sequence_number=None, **kwargs): :param int max_message_count: The maximum number of messages to try and peek. The default value is 1. - :param int sequence_number: A message sequence number from which to start browsing messages. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword int sequence_number: A message sequence number from which to start browsing messages. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: List[~azure.servicebus.PeekedMessage] @@ -492,7 +496,10 @@ def peek_messages(self, max_message_count=1, sequence_number=None, **kwargs): """ self._check_live() + sequence_number = kwargs.pop("sequence_number", 0) timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if not sequence_number: sequence_number = self._last_received_sequenced_number or 1 if int(max_message_count) < 1: diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py index b5d79bccd83..a7eb179d009 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_sender.py @@ -188,8 +188,12 @@ def _open(self): def _send(self, message, timeout=None, last_exception=None): # type: (Message, Optional[float], Exception) -> None self._open() - self._set_msg_timeout(timeout, last_exception) - self._handler.send_message(message.message) + default_timeout = self._handler._msg_timeout # pylint: disable=protected-access + try: + self._set_msg_timeout(timeout, last_exception) + self._handler.send_message(message.message) + finally: # reset the timeout of the handler back to the default value + self._set_msg_timeout(default_timeout, None) def schedule_messages(self, messages, schedule_time_utc, **kwargs): # type: (Union[Message, List[Message]], datetime.datetime, Any) -> List[int] @@ -199,7 +203,8 @@ def schedule_messages(self, messages, schedule_time_utc, **kwargs): :type messages: Union[~azure.servicebus.Message, List[~azure.servicebus.Message]] :param schedule_time_utc: The utc date and time to enqueue the messages. :type schedule_time_utc: ~datetime.datetime - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: List[int] .. admonition:: Example: @@ -214,6 +219,8 @@ def schedule_messages(self, messages, schedule_time_utc, **kwargs): # pylint: disable=protected-access self._open() timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if isinstance(messages, Message): request_body = self._build_schedule_request(schedule_time_utc, messages) else: @@ -232,7 +239,8 @@ def cancel_scheduled_messages(self, sequence_numbers, **kwargs): :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. @@ -248,6 +256,8 @@ def cancel_scheduled_messages(self, sequence_numbers, **kwargs): """ self._open() timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if isinstance(sequence_numbers, int): numbers = [types.AMQPLong(sequence_numbers)] else: @@ -314,7 +324,8 @@ def send_messages(self, message, **kwargs): :param message: The ServiceBus message to be sent. :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage or list[~azure.servicebus.Message] - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: None :raises: :class: ~azure.servicebus.exceptions.OperationTimeoutError if sending times out. @@ -336,6 +347,8 @@ def send_messages(self, message, **kwargs): """ timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") message = transform_messages_to_sendable_if_needed(message) try: batch = self.create_batch() @@ -352,7 +365,7 @@ def send_messages(self, message, **kwargs): self._send, message=message, timeout=timeout, - require_timeout=True, + operation_requires_timeout=True, require_last_exception=True ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py index bf317ee5277..5ed76d9d3cb 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/_servicebus_session.py @@ -97,7 +97,8 @@ def get_state(self, **kwargs): Returns None if no state has been set. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: str .. admonition:: Example: @@ -111,6 +112,8 @@ def get_state(self, **kwargs): """ self._check_live() timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") response = self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._session_id}, @@ -129,7 +132,8 @@ def set_state(self, state, **kwargs): :param state: The state value. :type state: Union[str, bytes, bytearray] - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. .. admonition:: Example: @@ -142,6 +146,8 @@ def set_state(self, state, **kwargs): """ self._check_live() timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") state = state.encode(self._encoding) if isinstance(state, six.text_type) else state return self._receiver._mgmt_request_response_with_retry( # type: ignore REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, @@ -163,7 +169,8 @@ def renew_lock(self, **kwargs): This operation can also be performed as a threaded background task by registering the session with an `azure.servicebus.AutoLockRenew` instance. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime @@ -178,6 +185,8 @@ def renew_lock(self, **kwargs): """ self._check_live() timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") expiry = self._receiver._mgmt_request_response_with_retry( REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, {MGMT_REQUEST_SESSION_ID: self._session_id}, diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py index 3fd53fdf5cb..afaa5327527 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_async_message.py @@ -140,7 +140,8 @@ async def renew_lock(self, **kwargs: Any) -> datetime.datetime: background task by registering the message with an `azure.servicebus.aio.AutoLockRenew` instance. This operation is only available for non-sessionful messages. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :returns: The utc datetime the lock is set to expire at. :rtype: datetime.datetime :raises: TypeError if the message is sessionful. @@ -148,7 +149,6 @@ async def renew_lock(self, **kwargs: Any) -> datetime.datetime: :raises: ~azure.servicebus.exceptions.SessionLockExpired if session lock has already expired. :raises: ~azure.servicebus.exceptions.MessageAlreadySettled is message has already been settled. """ - timeout = kwargs.pop("timeout", None) try: if self._receiver.session: # type: ignore raise TypeError("Session messages cannot be renewed. Please renew the Session lock instead.") @@ -159,7 +159,11 @@ async def renew_lock(self, **kwargs: Any) -> datetime.datetime: if not token: raise ValueError("Unable to renew lock - no lock token found.") - expiry = await self._receiver._renew_locks(token, timeout=timeout) # type: ignore - self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") + + expiry = await self._receiver._renew_locks(token, timeout=timeout) # type: ignore + self._expiry = utc_from_timestamp(expiry[MGMT_RESPONSE_MESSAGE_EXPIRATION][0]/1000.0) # type: datetime.datetime return self._expiry diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py index 2fad46f4abe..c9ac9923c81 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_base_handler_async.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Dict import uamqp +from uamqp import compat from uamqp.message import MessageProperties from azure.core.credentials import AccessToken @@ -23,6 +24,7 @@ CONTAINER_PREFIX, MANAGEMENT_PATH_SUFFIX) from ..exceptions import ( ServiceBusError, + OperationTimeoutError, _create_servicebus_exception ) @@ -90,7 +92,7 @@ def __init__( self._container_id = CONTAINER_PREFIX + str(uuid.uuid4())[:8] self._config = Configuration(**kwargs) self._running = False - self._handler = None # type: uamqp.AMQPClient + self._handler = None # type: uamqp.AMQPClientAsync self._auth_uri = None self._properties = create_properties(self._config.user_agent) @@ -150,16 +152,15 @@ async def _backoff( async def _do_retryable_operation(self, operation, timeout=None, **kwargs): # type: (Callable, Optional[float], Any) -> Any require_last_exception = kwargs.pop("require_last_exception", False) - require_timeout = kwargs.pop("require_timeout", False) + operation_requires_timeout = kwargs.pop("operation_requires_timeout", False) retried_times = 0 - last_exception = None max_retries = self._config.retry_total - abs_timeout_time = (time.time() + timeout) if (require_timeout and timeout) else None + abs_timeout_time = (time.time() + timeout) if (operation_requires_timeout and timeout) else None while retried_times <= max_retries: try: - if require_timeout and abs_timeout_time: + if operation_requires_timeout and abs_timeout_time: remaining_timeout = abs_timeout_time - time.time() kwargs["timeout"] = remaining_timeout return await operation(**kwargs) @@ -193,6 +194,21 @@ async def _mgmt_request_response( **kwargs ): # type: (bytes, uamqp.Message, Callable, bool, Optional[float], Any) -> uamqp.Message + """ + Execute an amqp management operation. + + :param bytes mgmt_operation: The type of operation to be performed. This value will + be service-specific, but common values include READ, CREATE and UPDATE. + This value will be added as an application property on the message. + :param message: The message to send in the management request. + :paramtype message: ~uamqp.message.Message + :param callback: The callback which is used to parse the returning message. + :paramtype callback: Callable[int, ~uamqp.message.Message, str] + :param keep_alive_associated_link: A boolean flag for keeping associated amqp sender/receiver link alive when + executing operation on mgmt links. + :param timeout: timeout in seconds for executing the mgmt operation. + :rtype: None + """ await self._open() application_properties = {} @@ -219,6 +235,8 @@ async def _mgmt_request_response( timeout=timeout * 1000 if timeout else None, callback=callback) except Exception as exp: # pylint: disable=broad-except + if isinstance(exp, compat.TimeoutException): + raise OperationTimeoutError("Management operation timed out.", inner_exception=exp) raise ServiceBusError("Management request failed: {}".format(exp), exp) async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callback, timeout=None, **kwargs): @@ -229,7 +247,7 @@ async def _mgmt_request_response_with_retry(self, mgmt_operation, message, callb message=message, callback=callback, timeout=timeout, - require_timeout=True, + operation_requires_timeout=True, **kwargs ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py index 2115b73615b..099a64a8245 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_receiver_async.py @@ -209,11 +209,6 @@ async def _open(self): await self.close() raise - async def close(self): - # type: () -> None - await super(ServiceBusReceiver, self).close() - self._message_iter = None - async def _receive(self, max_message_count=None, timeout=None): # type: (Optional[int], Optional[float]) -> List[ReceivedMessage] # pylint: disable=protected-access @@ -283,6 +278,11 @@ async def _renew_locks(self, *lock_tokens, timeout=None): timeout=timeout ) + async def close(self): + # type: () -> None + await super(ServiceBusReceiver, self).close() + self._message_iter = None + def get_streaming_message_iter(self, max_wait_time: float = None) -> AsyncIterator[ReceivedMessage]: """Receive messages from an iterator indefinitely, or if a max_wait_time is specified, until such a timeout occurs. @@ -407,7 +407,7 @@ async def receive_messages(self, max_message_count=None, max_wait_time=None): self._receive, max_message_count=max_message_count, timeout=max_wait_time, - require_timeout=True + operation_requires_timeout=True ) async def receive_deferred_messages(self, sequence_numbers, **kwargs): @@ -419,7 +419,8 @@ async def receive_deferred_messages(self, sequence_numbers, **kwargs): :param Union[int, list[int]] sequence_numbers: A list of the sequence numbers of messages that have been deferred. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: list[~azure.servicebus.aio.ReceivedMessage] .. admonition:: Example: @@ -432,8 +433,10 @@ async def receive_deferred_messages(self, sequence_numbers, **kwargs): :caption: Receive deferred messages from ServiceBus. """ - timeout = kwargs.pop("timeout", None) self._check_live() + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if isinstance(sequence_numbers, six.integer_types): sequence_numbers = [sequence_numbers] if not sequence_numbers: @@ -472,7 +475,8 @@ async def peek_messages(self, max_message_count=1, **kwargs): :param int max_message_count: The maximum number of messages to try and peek. The default value is 1. :keyword int sequence_number: A message sequence number from which to start browsing messages. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: list[~azure.servicebus.PeekedMessage] .. admonition:: Example: @@ -484,9 +488,11 @@ async def peek_messages(self, max_message_count=1, **kwargs): :dedent: 4 :caption: Peek messages in the queue. """ + self._check_live() sequence_number = kwargs.pop("sequence_number", 0) timeout = kwargs.pop("timeout", None) - self._check_live() + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if not sequence_number: sequence_number = self._last_received_sequenced_number or 1 if int(max_message_count) < 1: diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py index 8060017f4e5..483de912fca 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_sender_async.py @@ -131,8 +131,12 @@ async def _open(self): async def _send(self, message, timeout=None, last_exception=None): await self._open() - self._set_msg_timeout(timeout, last_exception) - await self._handler.send_message_async(message.message) + default_timeout = self._handler._msg_timeout # pylint: disable=protected-access + try: + self._set_msg_timeout(timeout, last_exception) + await self._handler.send_message_async(message.message) + finally: # reset the timeout of the handler back to the default value + self._set_msg_timeout(default_timeout, None) async def schedule_messages(self, messages, schedule_time_utc, **kwargs): # type: (Union[Message, List[Message]], datetime.datetime, Any) -> List[int] @@ -142,7 +146,8 @@ async def schedule_messages(self, messages, schedule_time_utc, **kwargs): :type messages: ~azure.servicebus.Message or list[~azure.servicebus.Message] :param schedule_time_utc: The utc date and time to enqueue the messages. :type schedule_time_utc: ~datetime.datetime - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: list[int] .. admonition:: Example: @@ -155,8 +160,10 @@ async def schedule_messages(self, messages, schedule_time_utc, **kwargs): :caption: Schedule a message to be sent in future """ # pylint: disable=protected-access - timeout = kwargs.pop("timeout", None) await self._open() + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if isinstance(messages, Message): request_body = self._build_schedule_request(schedule_time_utc, messages) else: @@ -175,7 +182,8 @@ async def cancel_scheduled_messages(self, sequence_numbers, **kwargs): :param sequence_numbers: The sequence numbers of the scheduled messages. :type sequence_numbers: int or list[int] - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: None :raises: ~azure.servicebus.exceptions.ServiceBusError if messages cancellation failed due to message already cancelled or enqueued. @@ -189,8 +197,10 @@ async def cancel_scheduled_messages(self, sequence_numbers, **kwargs): :dedent: 4 :caption: Cancelling messages scheduled to be sent in future """ - timeout = kwargs.pop("timeout", None) await self._open() + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") if isinstance(sequence_numbers, int): numbers = [types.AMQPLong(sequence_numbers)] else: @@ -252,7 +262,8 @@ async def send_messages(self, message, **kwargs): :param message: The ServiceBus message to be sent. :type message: ~azure.servicebus.Message or ~azure.servicebus.BatchMessage or list[~azure.servicebus.Message] - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: None :raises: :class: ~azure.servicebus.exceptions.OperationTimeoutError if sending times out. @@ -274,6 +285,8 @@ async def send_messages(self, message, **kwargs): """ timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") message = transform_messages_to_sendable_if_needed(message) try: batch = await self.create_batch() @@ -290,7 +303,7 @@ async def send_messages(self, message, **kwargs): self._send, message=message, timeout=timeout, - require_timeout=True, + operation_requires_timeout=True, require_last_exception=True ) diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py index 8c6cd8db308..deec3792fa3 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/aio/_servicebus_session_async.py @@ -46,7 +46,8 @@ async def get_state(self, **kwargs): Returns None if no state has been set. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: str .. admonition:: Example: @@ -58,8 +59,10 @@ async def get_state(self, **kwargs): :dedent: 4 :caption: Get the session state """ - timeout = kwargs.pop("timeout", None) self._check_live() + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") response = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_GET_SESSION_STATE_OPERATION, {MGMT_REQUEST_SESSION_ID: self._session_id}, @@ -77,7 +80,8 @@ async def set_state(self, state, **kwargs): :param state: The state value. :type state: Union[str, bytes, bytearray] - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :rtype: None .. admonition:: Example: @@ -89,8 +93,10 @@ async def set_state(self, state, **kwargs): :dedent: 4 :caption: Set the session state """ - timeout = kwargs.pop("timeout", None) self._check_live() + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") state = state.encode(self._encoding) if isinstance(state, six.text_type) else state return await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_SET_SESSION_STATE_OPERATION, @@ -111,7 +117,8 @@ async def renew_lock(self, **kwargs): This operation can also be performed as a threaded background task by registering the session with an `azure.servicebus.aio.AutoLockRenew` instance. - :keyword float timeout: The total operation timeout in seconds including all the retries. + :keyword float timeout: The total operation timeout in seconds including all the retries. The value must be + greater than 0 if specified. The default value is None, meaning no timeout. :returns: The utc datetime the lock is set to expire at. :rtype: datetime @@ -124,8 +131,10 @@ async def renew_lock(self, **kwargs): :dedent: 4 :caption: Renew the session lock before it expires """ - timeout = kwargs.pop("timeout", None) self._check_live() + timeout = kwargs.pop("timeout", None) + if timeout is not None and timeout <= 0: + raise ValueError("The timeout must be greater than 0.") expiry = await self._receiver._mgmt_request_response_with_retry( # pylint: disable=protected-access REQUEST_RESPONSE_RENEW_SESSION_LOCK_OPERATION, {MGMT_REQUEST_SESSION_ID: self._session_id}, diff --git a/sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py b/sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py index 5540709362d..202730ddcfa 100644 --- a/sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py +++ b/sdk/servicebus/azure-servicebus/azure/servicebus/exceptions.py @@ -81,7 +81,10 @@ def _create_servicebus_exception(logger, exception, handler): # pylint: disable error_need_raise = True elif isinstance(exception, errors.MessageException): logger.info("Message send failed (%r)", exception) - error = MessageSendFailed(exception) + if exception.condition == constants.ErrorCodes.ClientError and 'timed out' in str(exception): + error = OperationTimeoutError("Send operation timed out", inner_exception=exception) + else: + error = MessageSendFailed(exception) error_need_raise = False elif isinstance(exception, errors.LinkDetach) and exception.condition == SESSION_LOCK_LOST: try: diff --git a/sdk/servicebus/azure-servicebus/setup.py b/sdk/servicebus/azure-servicebus/setup.py index a66b6aec3de..84802e7402b 100644 --- a/sdk/servicebus/azure-servicebus/setup.py +++ b/sdk/servicebus/azure-servicebus/setup.py @@ -78,7 +78,7 @@ 'azure', ]), install_requires=[ - 'uamqp>=1.2.10,<2.0.0', + 'uamqp>=1.2.11,<2.0.0', 'azure-common~=1.1', 'msrest>=0.6.17,<2.0.0', 'azure-core<2.0.0,>=1.6.0', diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index 42ad5035980..eff4ec134e3 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -8,11 +8,14 @@ import logging import sys import os +import types import pytest import time import uuid from datetime import datetime, timedelta +import uamqp +from uamqp import compat from azure.servicebus.aio import ( ServiceBusClient, ReceivedMessage, @@ -29,7 +32,9 @@ AutoLockRenewTimeout, MessageSendFailed, MessageSettleFailed, - MessageContentTooLarge) + MessageContentTooLarge, + OperationTimeoutError +) from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer from servicebus_preparer import CachedServiceBusNamespacePreparer, CachedServiceBusQueuePreparer, ServiceBusQueuePreparer from utilities import get_logger, print_message, sleep_until_expired @@ -53,7 +58,9 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel for i in range(10): message = Message("Handler message no. {}".format(i)) await sender.send_messages(message, timeout=5) - assert 4990 <= sender._handler._msg_timeout <= 5000 # initial retry logic consumes cycles, usually just 1ms + # This is checking the timeout config on the amqp sender, it doesn't imply the send success or failure + # as there could be ~1ms gap before the code executes into setting the remaining time on the amqp sender + assert 4990 <= sender._handler._msg_timeout <= 5000 with pytest.raises(ServiceBusConnectionError): await (sb_client.get_queue_session_receiver(servicebus_queue.name, session_id="test", max_wait_time=5))._open_with_retry() @@ -359,7 +366,9 @@ async def test_async_queue_by_servicebus_client_iter_messages_with_retrieve_defe assert count == 10 async with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5) as session: - deferred = await session.receive_deferred_messages(deferred_messages, timeout=0) + with pytest.raises(ValueError): + await session.receive_deferred_messages(deferred_messages, timeout=0) + deferred = await session.receive_deferred_messages(deferred_messages) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -645,7 +654,7 @@ async def test_async_queue_by_servicebus_client_browse_empty_messages(self, serv servicebus_namespace_connection_string, logging_enable=False) as sb_client: async with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5, receive_mode=ReceiveMode.PeekLock, prefetch_count=10) as receiver: - messages = await receiver.peek_messages(10, timeout=0) + messages = await receiver.peek_messages(10) assert len(messages) == 0 @pytest.mark.liveTest @@ -884,7 +893,7 @@ async def test_async_queue_message_lock_renew(self, servicebus_namespace_connect time.sleep(15) await messages[0].renew_lock(timeout=5) time.sleep(15) - await messages[0].renew_lock(timeout=0) + await messages[0].renew_lock() time.sleep(15) assert not messages[0]._lock_expired await messages[0].complete() @@ -1052,7 +1061,7 @@ async def test_async_queue_cancel_scheduled_messages(self, servicebus_namespace_ async with sb_client.get_queue_sender(servicebus_queue.name) as sender: message_a = Message("Test scheduled message") message_b = Message("Test scheduled message") - tokens = await sender.schedule_messages([message_a, message_b], enqueue_time, timeout=0) + tokens = await sender.schedule_messages([message_a, message_b], enqueue_time) assert len(tokens) == 2 await sender.cancel_scheduled_messages(tokens, timeout=None) @@ -1443,3 +1452,60 @@ async def test_async_queue_send_twice(self, servicebus_namespace_connection_stri async for message in receiver: messages.append(message) assert len(messages) == 2 + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + async def test_async_queue_send_timeout(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async def _hack_amqp_sender_run_async(cls): + await asyncio.sleep(6) # sleep until timeout + await cls.message_handler.work_async() + cls._waiting_messages = 0 + cls._pending_messages = cls._filter_pending() + if cls._backoff and not cls._waiting_messages: + _logger.info("Client told to backoff - sleeping for %r seconds", cls._backoff) + await cls._connection.sleep_async(cls._backoff) + cls._backoff = 0 + await cls._connection.work_async() + return True + + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + sender._handler._client_run_async = types.MethodType(_hack_amqp_sender_run_async, sender._handler) + with pytest.raises(OperationTimeoutError): + await sender.send_messages(Message("body"), timeout=5) + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + async def test_async_queue_mgmt_operation_timeout(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + async def hack_mgmt_execute_async(self, operation, op_type, message, timeout=0): + start_time = self._counter.get_current_ms() + operation_id = str(uuid.uuid4()) + self._responses[operation_id] = None + + await asyncio.sleep(6) # sleep until timeout + while not self._responses[operation_id] and not self.mgmt_error: + if timeout > 0: + now = self._counter.get_current_ms() + if (now - start_time) >= timeout: + raise compat.TimeoutException("Failed to receive mgmt response in {}ms".format(timeout)) + await self.connection.work_async() + if self.mgmt_error: + raise self.mgmt_error + response = self._responses.pop(operation_id) + return response + + uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = hack_mgmt_execute_async + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(OperationTimeoutError): + scheduled_time_utc = utc_now() + timedelta(seconds=30) + await sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) + diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index 69ec8e70f44..14b63ac4247 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -7,6 +7,7 @@ import logging import sys import os +import types import pytest import time import uuid @@ -14,6 +15,7 @@ import calendar import uamqp +from uamqp import compat from azure.servicebus import ServiceBusClient, AutoLockRenew, TransportType from azure.servicebus._common.message import Message, PeekedMessage, ReceivedMessage, BatchMessage from azure.servicebus._common.constants import ( @@ -33,7 +35,9 @@ AutoLockRenewTimeout, MessageSendFailed, MessageSettleFailed, - MessageContentTooLarge) + MessageContentTooLarge, + OperationTimeoutError +) from devtools_testutils import AzureMgmtTestCase, CachedResourceGroupPreparer from servicebus_preparer import CachedServiceBusNamespacePreparer, ServiceBusQueuePreparer, CachedServiceBusQueuePreparer @@ -465,7 +469,9 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receive with sb_client.get_queue_receiver(servicebus_queue.name, max_wait_time=5, receive_mode=ReceiveMode.PeekLock) as receiver: - deferred = receiver.receive_deferred_messages(deferred_messages, timeout=0) + with pytest.raises(ValueError): + receiver.receive_deferred_messages(deferred_messages, timeout=0) + deferred = receiver.receive_deferred_messages(deferred_messages) assert len(deferred) == 10 for message in deferred: assert isinstance(message, ReceivedMessage) @@ -484,7 +490,7 @@ def test_queue_by_servicebus_client_iter_messages_with_retrieve_deferred_receive with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: - + with sb_client.get_queue_sender(servicebus_queue.name) as sender: deferred_messages = [] for i in range(10): @@ -774,7 +780,7 @@ def test_queue_by_servicebus_client_browse_messages_with_receiver(self, serviceb ) sender.send_messages(message) - messages = receiver.peek_messages(5, timeout=0) + messages = receiver.peek_messages(5) assert len(messages) > 0 assert all(isinstance(m, PeekedMessage) for m in messages) for message in messages: @@ -1083,9 +1089,9 @@ def test_queue_message_lock_renew(self, servicebus_namespace_connection_string, messages = receiver.receive_messages(max_wait_time=10) assert len(messages) == 1 time.sleep(15) - messages[0].renew_lock(timeout=0) + messages[0].renew_lock() time.sleep(15) - messages[0].renew_lock(timeout=0) + messages[0].renew_lock() time.sleep(15) assert not messages[0]._lock_expired messages[0].complete() @@ -1320,7 +1326,7 @@ def test_queue_cancel_scheduled_messages(self, servicebus_namespace_connection_s with sb_client.get_queue_sender(servicebus_queue.name) as sender: message_a = Message("Test scheduled message") message_b = Message("Test scheduled message") - tokens = sender.schedule_messages([message_a, message_b], enqueue_time, timeout=0) + tokens = sender.schedule_messages([message_a, message_b], enqueue_time) assert len(tokens) == 2 sender.cancel_scheduled_messages(tokens, timeout=None) @@ -1900,7 +1906,62 @@ def test_message_inner_amqp_properties(self, servicebus_namespace_connection_str assert message.amqp_message.properties.subject == b"subject" assert message.amqp_message.application_properties[b"application_properties"] == 1 assert message.amqp_message.annotations[b"annotations"] == 2 - # delivery_annotations and footer disabled pending uamqp bug https://github.com/Azure/azure-uamqp-python/issues/169 - #assert message.amqp_message.delivery_annotations[b"delivery_annotations"] == 3 + assert message.amqp_message.delivery_annotations[b"delivery_annotations"] == 3 assert message.amqp_message.header.priority == 5 - #assert message.amqp_message.footer[b"footer"] == 6 \ No newline at end of file + assert message.amqp_message.footer[b"footer"] == 6 + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + def test_queue_send_timeout(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + def _hack_amqp_sender_run(cls): + time.sleep(6) # sleep until timeout + cls.message_handler.work() + cls._waiting_messages = 0 + cls._pending_messages = cls._filter_pending() + if cls._backoff and not cls._waiting_messages: + _logger.info("Client told to backoff - sleeping for %r seconds", cls._backoff) + cls._connection.sleep(cls._backoff) + cls._backoff = 0 + cls._connection.work() + return True + + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + sender._handler._client_run = types.MethodType(_hack_amqp_sender_run, sender._handler) + with pytest.raises(OperationTimeoutError): + sender.send_messages(Message("body"), timeout=5) + + @pytest.mark.liveTest + @pytest.mark.live_test_only + @CachedResourceGroupPreparer(name_prefix='servicebustest') + @CachedServiceBusNamespacePreparer(name_prefix='servicebustest') + @CachedServiceBusQueuePreparer(name_prefix='servicebustest', dead_lettering_on_message_expiration=True) + def test_queue_mgmt_operation_timeout(self, servicebus_namespace_connection_string, servicebus_queue, **kwargs): + def hack_mgmt_execute(self, operation, op_type, message, timeout=0): + start_time = self._counter.get_current_ms() + operation_id = str(uuid.uuid4()) + self._responses[operation_id] = None + + time.sleep(6) # sleep until timeout + while not self._responses[operation_id] and not self.mgmt_error: + if timeout > 0: + now = self._counter.get_current_ms() + if (now - start_time) >= timeout: + raise compat.TimeoutException("Failed to receive mgmt response in {}ms".format(timeout)) + self.connection.work() + if self.mgmt_error: + raise self.mgmt_error + response = self._responses.pop(operation_id) + return response + + uamqp.mgmt_operation.MgmtOperation.execute = hack_mgmt_execute + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(OperationTimeoutError): + scheduled_time_utc = utc_now() + timedelta(seconds=30) + sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) diff --git a/shared_requirements.txt b/shared_requirements.txt index 714c7c2be20..bb699cc1fdc 100644 --- a/shared_requirements.txt +++ b/shared_requirements.txt @@ -153,7 +153,7 @@ opentelemetry-api==0.10b0 #override azure-eventhub-checkpointstoreblob-aio aiohttp<4.0,>=3.0 #override azure-eventhub uamqp<2.0,>=1.2.7 #override azure-appconfiguration msrest>=0.6.10 -#override azure-servicebus uamqp>=1.2.10,<2.0.0 +#override azure-servicebus uamqp>=1.2.11,<2.0.0 #override azure-servicebus msrest>=0.6.17,<2.0.0 #override azure-servicebus azure-core<2.0.0,>=1.6.0 #override azure-search-documents msrest>=0.6.10 From d5f095d5b1f9124bfb0d09c3ba1b06e6e03a3770 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Wed, 30 Sep 2020 13:59:11 -0700 Subject: [PATCH 13/18] remove configuration check --- .../azure-servicebus/tests/async_tests/test_queues_async.py | 3 --- sdk/servicebus/azure-servicebus/tests/test_queues.py | 1 - 2 files changed, 4 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index eff4ec134e3..b3d1ef22e27 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -58,9 +58,6 @@ async def test_async_queue_by_queue_client_conn_str_receive_handler_peeklock(sel for i in range(10): message = Message("Handler message no. {}".format(i)) await sender.send_messages(message, timeout=5) - # This is checking the timeout config on the amqp sender, it doesn't imply the send success or failure - # as there could be ~1ms gap before the code executes into setting the remaining time on the amqp sender - assert 4990 <= sender._handler._msg_timeout <= 5000 with pytest.raises(ServiceBusConnectionError): await (sb_client.get_queue_session_receiver(servicebus_queue.name, session_id="test", max_wait_time=5))._open_with_retry() diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index 14b63ac4247..c193b36d7fc 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -66,7 +66,6 @@ def test_receive_and_delete_reconnect_interaction(self, servicebus_namespace_con with sb_client.get_queue_sender(servicebus_queue.name) as sender: for i in range(5): sender.send_messages(Message("Message {}".format(i)), timeout=5) - assert 4990 <= sender._handler._msg_timeout <= 5000 # initial retry logic consumes some cycles, usually just 1ms with sb_client.get_queue_receiver(servicebus_queue.name, receive_mode=ReceiveMode.ReceiveAndDelete, From 85ff115780db69cac5ac699b15b0904b32b133c5 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Sun, 4 Oct 2020 20:07:12 -0700 Subject: [PATCH 14/18] fix bug in test hacking --- .../tests/async_tests/test_queues_async.py | 5 +++++ sdk/servicebus/azure-servicebus/tests/test_queues.py | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index b3d1ef22e27..fc918e05daa 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -1471,6 +1471,7 @@ async def _hack_amqp_sender_run_async(cls): async with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + # this one doesn't need to reset the method, as it's hacking the method on the instance sender._handler._client_run_async = types.MethodType(_hack_amqp_sender_run_async, sender._handler) with pytest.raises(OperationTimeoutError): await sender.send_messages(Message("body"), timeout=5) @@ -1498,6 +1499,8 @@ async def hack_mgmt_execute_async(self, operation, op_type, message, timeout=0): response = self._responses.pop(operation_id) return response + original_execute_method = uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async + # hack the mgmt method on the class, not on an instance, so it needs reset uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = hack_mgmt_execute_async async with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: @@ -1506,3 +1509,5 @@ async def hack_mgmt_execute_async(self, operation, op_type, message, timeout=0): scheduled_time_utc = utc_now() + timedelta(seconds=30) await sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) + # must reset the mgmt execute method, otherwise other test cases would use the hacked execute method, leading to timeout error + uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = original_execute_method diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index 9ff5acd5072..e2877428511 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -1937,6 +1937,7 @@ def _hack_amqp_sender_run(cls): with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: with sb_client.get_queue_sender(servicebus_queue.name) as sender: + # this one doesn't need to reset the method, as it's hacking the method on the instance sender._handler._client_run = types.MethodType(_hack_amqp_sender_run, sender._handler) with pytest.raises(OperationTimeoutError): sender.send_messages(Message("body"), timeout=5) @@ -1964,10 +1965,15 @@ def hack_mgmt_execute(self, operation, op_type, message, timeout=0): response = self._responses.pop(operation_id) return response + original_execute_method = uamqp.mgmt_operation.MgmtOperation.execute + # hack the mgmt method on the class, not on an instance, so it needs reset uamqp.mgmt_operation.MgmtOperation.execute = hack_mgmt_execute with ServiceBusClient.from_connection_string( servicebus_namespace_connection_string, logging_enable=False) as sb_client: with sb_client.get_queue_sender(servicebus_queue.name) as sender: with pytest.raises(OperationTimeoutError): scheduled_time_utc = utc_now() + timedelta(seconds=30) - sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) \ No newline at end of file + sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) + + # must reset the mgmt execute method, otherwise other test cases would use the hacked execute method, leading to timeout error + uamqp.mgmt_operation.MgmtOperation.execute = original_execute_method From be4f31f36658d75b68916455a7728656c49215ac Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Sun, 4 Oct 2020 21:50:37 -0700 Subject: [PATCH 15/18] fix bug where timeout=0 in tests --- sdk/servicebus/azure-servicebus/tests/test_sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/tests/test_sessions.py b/sdk/servicebus/azure-servicebus/tests/test_sessions.py index f958d1229cf..a8ff86695eb 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_sessions.py +++ b/sdk/servicebus/azure-servicebus/tests/test_sessions.py @@ -730,7 +730,7 @@ def test_session_schedule_message(self, servicebus_namespace_connection_string, count = 0 while not messages and count < 12: messages = receiver.receive_messages(max_wait_time=10) - receiver.session.renew_lock(timeout=0) + receiver.session.renew_lock(timeout=None) count += 1 data = str(messages[0]) @@ -769,7 +769,7 @@ def test_session_schedule_multiple_messages(self, servicebus_namespace_connectio messages = [] count = 0 while len(messages) < 2 and count < 12: - receiver.session.renew_lock(timeout=0) + receiver.session.renew_lock(timeout=None) messages = receiver.receive_messages(max_wait_time=15) time.sleep(5) count += 1 From e793bb2f54e5f1feff6b9bccd1a18aed802e07f8 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Mon, 5 Oct 2020 14:58:54 -0700 Subject: [PATCH 16/18] tweak changelog position --- sdk/servicebus/azure-servicebus/CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index ba997645de1..1f21740a207 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -2,9 +2,6 @@ ## 7.0.0b8 (Unreleased) - -## 7.0.0b7 (2020-10-05) - **New Features** * Added support for `timeout` parameter on the following operations: @@ -13,6 +10,8 @@ - `ServiceBusSession`: `get_state`, `set_state` and `renew_lock` - `ReceivedMessage`: `renew_lock` +## 7.0.0b7 (2020-10-05) + **Breaking Changes** * Passing any type other than `ReceiveMode` as parameter `receive_mode` now throws a `TypeError` instead of `AttributeError`. From e1b5721fccb5497789cbf1d0247ed9f6bb3fdfd6 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Mon, 5 Oct 2020 15:09:13 -0700 Subject: [PATCH 17/18] update changelog --- sdk/servicebus/azure-servicebus/CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/CHANGELOG.md b/sdk/servicebus/azure-servicebus/CHANGELOG.md index 1f21740a207..0b908ed0a30 100644 --- a/sdk/servicebus/azure-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-servicebus/CHANGELOG.md @@ -10,6 +10,11 @@ - `ServiceBusSession`: `get_state`, `set_state` and `renew_lock` - `ReceivedMessage`: `renew_lock` +**BugFixes** + +* Updated uAMQP dependency to 1.2.11. + - Fixed bug where amqp message `footer` and `delivery_annotation` were not encoded into the outgoing payload. + ## 7.0.0b7 (2020-10-05) **Breaking Changes** @@ -18,11 +23,6 @@ * Administration Client calls now take only entity names, not `Descriptions` as well to reduce ambiguity in which entity was being acted on. TypeError will now be thrown on improper parameter types (non-string). * `AMQPMessage` (`Message.amqp_message`) properties are now read-only, changes of these properties would not be reflected in the underlying message. This may be subject to change before GA. -**BugFixes** - -* Updated uAMQP dependency to 1.2.11. - - Fixed bug where amqp message `footer` and `delivery_annotation` were not encoded into the outgoing payload. - ## 7.0.0b6 (2020-09-10) **New Features** From 840bcf2391a175170bb34839c1c2588323e05573 Mon Sep 17 00:00:00 2001 From: Yunhao Ling Date: Tue, 6 Oct 2020 14:22:35 -0700 Subject: [PATCH 18/18] add try-finally in timeout test cases --- .../tests/async_tests/test_queues_async.py | 21 ++++++++++--------- .../azure-servicebus/tests/test_queues.py | 20 ++++++++++-------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py index fc918e05daa..5cc213603be 100644 --- a/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py +++ b/sdk/servicebus/azure-servicebus/tests/async_tests/test_queues_async.py @@ -1501,13 +1501,14 @@ async def hack_mgmt_execute_async(self, operation, op_type, message, timeout=0): original_execute_method = uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async # hack the mgmt method on the class, not on an instance, so it needs reset - uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = hack_mgmt_execute_async - async with ServiceBusClient.from_connection_string( - servicebus_namespace_connection_string, logging_enable=False) as sb_client: - async with sb_client.get_queue_sender(servicebus_queue.name) as sender: - with pytest.raises(OperationTimeoutError): - scheduled_time_utc = utc_now() + timedelta(seconds=30) - await sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) - - # must reset the mgmt execute method, otherwise other test cases would use the hacked execute method, leading to timeout error - uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = original_execute_method + try: + uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = hack_mgmt_execute_async + async with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + async with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(OperationTimeoutError): + scheduled_time_utc = utc_now() + timedelta(seconds=30) + await sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) + finally: + # must reset the mgmt execute method, otherwise other test cases would use the hacked execute method, leading to timeout error + uamqp.async_ops.mgmt_operation_async.MgmtOperationAsync.execute_async = original_execute_method diff --git a/sdk/servicebus/azure-servicebus/tests/test_queues.py b/sdk/servicebus/azure-servicebus/tests/test_queues.py index e2877428511..ea03cceb0d0 100644 --- a/sdk/servicebus/azure-servicebus/tests/test_queues.py +++ b/sdk/servicebus/azure-servicebus/tests/test_queues.py @@ -1967,13 +1967,15 @@ def hack_mgmt_execute(self, operation, op_type, message, timeout=0): original_execute_method = uamqp.mgmt_operation.MgmtOperation.execute # hack the mgmt method on the class, not on an instance, so it needs reset - uamqp.mgmt_operation.MgmtOperation.execute = hack_mgmt_execute - with ServiceBusClient.from_connection_string( - servicebus_namespace_connection_string, logging_enable=False) as sb_client: - with sb_client.get_queue_sender(servicebus_queue.name) as sender: - with pytest.raises(OperationTimeoutError): - scheduled_time_utc = utc_now() + timedelta(seconds=30) - sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) - # must reset the mgmt execute method, otherwise other test cases would use the hacked execute method, leading to timeout error - uamqp.mgmt_operation.MgmtOperation.execute = original_execute_method + try: + uamqp.mgmt_operation.MgmtOperation.execute = hack_mgmt_execute + with ServiceBusClient.from_connection_string( + servicebus_namespace_connection_string, logging_enable=False) as sb_client: + with sb_client.get_queue_sender(servicebus_queue.name) as sender: + with pytest.raises(OperationTimeoutError): + scheduled_time_utc = utc_now() + timedelta(seconds=30) + sender.schedule_messages(Message("Message to be scheduled"), scheduled_time_utc, timeout=5) + finally: + # must reset the mgmt execute method, otherwise other test cases would use the hacked execute method, leading to timeout error + uamqp.mgmt_operation.MgmtOperation.execute = original_execute_method