Skip to content

Commit ba91013

Browse files
authored
Fix 'NoneType' object has no attribute when querying during Session not available retry (Azure#37578)
* Add fixes to 'NoneType' object has no attribute when querying Fixes 'NoneType' object has no attribute when session retry is triggered and does not succesfully retry while querying. * Update CHANGELOG.md * update test files
1 parent dc7dc11 commit ba91013

File tree

7 files changed

+179
-4
lines changed

7 files changed

+179
-4
lines changed

sdk/cosmos/azure-cosmos/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#### Bugs Fixed
1111
* Consolidated Container Properties Cache to be in the Client to cache partition key definition and container rid to avoid unnecessary container reads. See [PR 35731](https://github.com/Azure/azure-sdk-for-python/pull/35731)
1212
* Fixed SDK regex validation that would not allow for item ids to be longer than 255 characters. See [PR 36569](https://github.com/Azure/azure-sdk-for-python/pull/36569).
13+
* Fixed issue where 'NoneType' object has no attribute error was raised when a session retry happened during a query. See [PR 37578](https://github.com/Azure/azure-sdk-for-python/pull/37578).
1314

1415
#### Other Changes
1516
* Getting offer thoughput when it has not been defined in a container will now give a 404/10004 instead of just a 404. See [PR 36043](https://github.com/Azure/azure-sdk-for-python/pull/36043)

sdk/cosmos/azure-cosmos/azure/cosmos/_endpoint_discovery_retry_policy.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ def ShouldRetry(self, exception): # pylint: disable=unused-argument
6666
:returns: a boolean stating whether the request should be retried
6767
:rtype: bool
6868
"""
69+
if not self.request:
70+
return False
71+
6972
if not self.connection_policy.EnableEndpointDiscovery:
7073
return False
7174

sdk/cosmos/azure-cosmos/azure/cosmos/_session_retry_policy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def ShouldRetry(self, _exception):
6767
:returns: a boolean stating whether the request should be retried
6868
:rtype: bool
6969
"""
70+
if not self.request:
71+
return False
7072
self.session_token_retry_count += 1
7173
# clear previous location-based routing directive
7274
self.request.clear_route_to_location()

sdk/cosmos/azure-cosmos/azure/cosmos/_timeout_failover_retry_policy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def __init__(self, connection_policy, global_endpoint_manager, *args):
2323
self.location_endpoint = self.global_endpoint_manager.resolve_service_endpoint(self.request)
2424

2525
def needsRetry(self):
26-
if self.args:
26+
if self.args and self.request:
2727
if (self.args[3].method == "GET") \
2828
or http_constants.HttpHeaders.IsQueryPlanRequest in self.args[3].headers\
2929
or http_constants.HttpHeaders.IsQuery in self.args[3].headers:

sdk/cosmos/azure-cosmos/azure/cosmos/aio/_retry_utility_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ async def ExecuteAsync(client, global_endpoint_manager, function, *args, **kwarg
177177
client.last_response_headers[
178178
HttpHeaders.ThrottleRetryWaitTimeInMs
179179
] = resourceThrottle_retry_policy.cumulative_wait_time_in_milliseconds
180-
if args and args[0].should_clear_session_token_on_session_read_failure:
180+
if args and args[0].should_clear_session_token_on_session_read_failure and client.session:
181181
client.session.clear_session_token(client.last_response_headers)
182182
raise
183183

sdk/cosmos/azure-cosmos/test/test_query.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import azure.cosmos.cosmos_client as cosmos_client
1313
import azure.cosmos.exceptions as exceptions
1414
import test_config
15-
from azure.cosmos import http_constants, DatabaseProxy
15+
from azure.cosmos import http_constants, DatabaseProxy, _endpoint_discovery_retry_policy
1616
from azure.cosmos._execution_context.base_execution_context import _QueryExecutionContextBase
1717
from azure.cosmos._execution_context.query_execution_info import _PartitionedQueryExecutionInfo
1818
from azure.cosmos.documents import _DistinctType
@@ -862,6 +862,90 @@ def test_computed_properties_query(self):
862862
self.assertEqual(len(queried_items), 0)
863863
self.created_db.delete_container(created_collection.id)
864864

865+
def test_query_request_params_none_retry_policy(self):
866+
created_collection = self.created_db.create_container(
867+
"query_request_params_none_retry_policy_" + str(uuid.uuid4()), PartitionKey(path="/pk"))
868+
items = [
869+
{'id': str(uuid.uuid4()), 'pk': 'test', 'val': 5},
870+
{'id': str(uuid.uuid4()), 'pk': 'test', 'val': 5},
871+
{'id': str(uuid.uuid4()), 'pk': 'test', 'val': 5}]
872+
873+
for item in items:
874+
created_collection.create_item(body=item)
875+
876+
self.OriginalExecuteFunction = retry_utility.ExecuteFunction
877+
# Test session retry will properly push the exception when retries run out
878+
retry_utility.ExecuteFunction = self._MockExecuteFunctionSessionRetry
879+
try:
880+
query = "SELECT * FROM c"
881+
items = created_collection.query_items(
882+
query=query,
883+
enable_cross_partition_query=True
884+
)
885+
fetch_results = list(items)
886+
except exceptions.CosmosHttpResponseError as e:
887+
self.assertEqual(e.status_code, 404)
888+
self.assertEqual(e.sub_status, 1002)
889+
890+
# Test endpoint discovery retry
891+
retry_utility.ExecuteFunction = self._MockExecuteFunctionEndPointRetry
892+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Max_retry_attempt_count = 3
893+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Retry_after_in_milliseconds = 10
894+
try:
895+
query = "SELECT * FROM c"
896+
items = created_collection.query_items(
897+
query=query,
898+
enable_cross_partition_query=True
899+
)
900+
fetch_results = list(items)
901+
except exceptions.CosmosHttpResponseError as e:
902+
self.assertEqual(e.status_code, http_constants.StatusCodes.FORBIDDEN)
903+
self.assertEqual(e.sub_status, http_constants.SubStatusCodes.WRITE_FORBIDDEN)
904+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Max_retry_attempt_count = 120
905+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Retry_after_in_milliseconds = 1000
906+
907+
# Finally lets test timeout failover retry
908+
retry_utility.ExecuteFunction = self._MockExecuteFunctionTimeoutFailoverRetry
909+
try:
910+
query = "SELECT * FROM c"
911+
items = created_collection.query_items(
912+
query=query,
913+
enable_cross_partition_query=True
914+
)
915+
fetch_results = list(items)
916+
except exceptions.CosmosHttpResponseError as e:
917+
self.assertEqual(e.status_code, http_constants.StatusCodes.REQUEST_TIMEOUT)
918+
retry_utility.ExecuteFunction = self.OriginalExecuteFunction
919+
retry_utility.ExecuteFunction = self.OriginalExecuteFunction
920+
self.created_db.delete_container(created_collection.id)
921+
922+
923+
def _MockExecuteFunctionSessionRetry(self, function, *args, **kwargs):
924+
if args:
925+
if args[1].operation_type == 'SqlQuery':
926+
ex_to_raise = exceptions.CosmosHttpResponseError(status_code=http_constants.StatusCodes.NOT_FOUND,
927+
message="Read Session is Not Available")
928+
ex_to_raise.sub_status = http_constants.SubStatusCodes.READ_SESSION_NOTAVAILABLE
929+
raise ex_to_raise
930+
return self.OriginalExecuteFunction(function, *args, **kwargs)
931+
932+
def _MockExecuteFunctionEndPointRetry(self, function, *args, **kwargs):
933+
if args:
934+
if args[1].operation_type == 'SqlQuery':
935+
ex_to_raise = exceptions.CosmosHttpResponseError(status_code=http_constants.StatusCodes.FORBIDDEN,
936+
message="End Point Discovery")
937+
ex_to_raise.sub_status = http_constants.SubStatusCodes.WRITE_FORBIDDEN
938+
raise ex_to_raise
939+
return self.OriginalExecuteFunction(function, *args, **kwargs)
940+
941+
def _MockExecuteFunctionTimeoutFailoverRetry(self, function, *args, **kwargs):
942+
if args:
943+
if args[1].operation_type == 'SqlQuery':
944+
ex_to_raise = exceptions.CosmosHttpResponseError(status_code=http_constants.StatusCodes.REQUEST_TIMEOUT,
945+
message="Timeout Failover")
946+
raise ex_to_raise
947+
return self.OriginalExecuteFunction(function, *args, **kwargs)
948+
865949
def _MockNextFunction(self):
866950
if self.count < len(self.payloads):
867951
item, result = self.get_mock_result(self.payloads, self.count)

sdk/cosmos/azure-cosmos/test/test_query_async.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import azure.cosmos.aio._retry_utility_async as retry_utility
1313
import azure.cosmos.exceptions as exceptions
1414
import test_config
15-
from azure.cosmos import http_constants
15+
from azure.cosmos import http_constants, _endpoint_discovery_retry_policy
1616
from azure.cosmos._execution_context.query_execution_info import _PartitionedQueryExecutionInfo
1717
from azure.cosmos.aio import CosmosClient, DatabaseProxy, ContainerProxy
1818
from azure.cosmos.documents import _DistinctType
@@ -936,6 +936,91 @@ async def query_items(database):
936936
self.client.client_connection.connection_policy.RetryOptions = old_retry
937937
await self.created_db.delete_container(created_collection.id)
938938

939+
async def test_query_request_params_none_retry_policy(self):
940+
created_collection = await self.created_db.create_container_if_not_exists(
941+
id="query_request_params_none_retry_policy_" + str(uuid.uuid4()),
942+
partition_key=PartitionKey(path="/pk")
943+
)
944+
items = [
945+
{'id': str(uuid.uuid4()), 'pk': 'test', 'val': 5},
946+
{'id': str(uuid.uuid4()), 'pk': 'test', 'val': 5},
947+
{'id': str(uuid.uuid4()), 'pk': 'test', 'val': 5}
948+
]
949+
950+
for item in items:
951+
await created_collection.create_item(body=item)
952+
953+
self.OriginalExecuteFunction = retry_utility.ExecuteFunctionAsync
954+
# Test session retry will properly push the exception when retries run out
955+
retry_utility.ExecuteFunctionAsync = self._MockExecuteFunctionSessionRetry
956+
try:
957+
query = "SELECT * FROM c"
958+
items = created_collection.query_items(
959+
query=query,
960+
enable_cross_partition_query=True
961+
)
962+
fetch_results = [item async for item in items]
963+
except exceptions.CosmosHttpResponseError as e:
964+
assert e.status_code == 404
965+
assert e.sub_status == 1002
966+
967+
# Test endpoint discovery retry
968+
retry_utility.ExecuteFunctionAsync = self._MockExecuteFunctionEndPointRetry
969+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Max_retry_attempt_count = 3
970+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Retry_after_in_milliseconds = 10
971+
try:
972+
query = "SELECT * FROM c"
973+
items = created_collection.query_items(
974+
query=query,
975+
enable_cross_partition_query=True
976+
)
977+
fetch_results = [item async for item in items]
978+
except exceptions.CosmosHttpResponseError as e:
979+
assert e.status_code == http_constants.StatusCodes.FORBIDDEN
980+
assert e.sub_status == http_constants.SubStatusCodes.WRITE_FORBIDDEN
981+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Max_retry_attempt_count = 120
982+
_endpoint_discovery_retry_policy.EndpointDiscoveryRetryPolicy.Retry_after_in_milliseconds = 1000
983+
984+
# Finally lets test timeout failover retry
985+
retry_utility.ExecuteFunctionAsync = self._MockExecuteFunctionTimeoutFailoverRetry
986+
try:
987+
query = "SELECT * FROM c"
988+
items = created_collection.query_items(
989+
query=query,
990+
enable_cross_partition_query=True
991+
)
992+
fetch_results = [item async for item in items]
993+
except exceptions.CosmosHttpResponseError as e:
994+
assert e.status_code == http_constants.StatusCodes.REQUEST_TIMEOUT
995+
retry_utility.ExecuteFunctionAsync = self.OriginalExecuteFunction
996+
await self.created_db.delete_container(created_collection.id)
997+
998+
async def _MockExecuteFunctionSessionRetry(self, function, *args, **kwargs):
999+
if args:
1000+
if args[1].operation_type == 'SqlQuery':
1001+
ex_to_raise = exceptions.CosmosHttpResponseError(status_code=http_constants.StatusCodes.NOT_FOUND,
1002+
message="Read Session is Not Available")
1003+
ex_to_raise.sub_status = http_constants.SubStatusCodes.READ_SESSION_NOTAVAILABLE
1004+
raise ex_to_raise
1005+
return await self.OriginalExecuteFunction(function, *args, **kwargs)
1006+
1007+
async def _MockExecuteFunctionEndPointRetry(self, function, *args, **kwargs):
1008+
if args:
1009+
if args[1].operation_type == 'SqlQuery':
1010+
ex_to_raise = exceptions.CosmosHttpResponseError(status_code=http_constants.StatusCodes.FORBIDDEN,
1011+
message="End Point Discovery")
1012+
ex_to_raise.sub_status = http_constants.SubStatusCodes.WRITE_FORBIDDEN
1013+
raise ex_to_raise
1014+
return await self.OriginalExecuteFunction(function, *args, **kwargs)
1015+
1016+
async def _MockExecuteFunctionTimeoutFailoverRetry(self, function, *args, **kwargs):
1017+
if args:
1018+
if args[1].operation_type == 'SqlQuery':
1019+
ex_to_raise = exceptions.CosmosHttpResponseError(status_code=http_constants.StatusCodes.REQUEST_TIMEOUT,
1020+
message="Timeout Failover")
1021+
raise ex_to_raise
1022+
return await self.OriginalExecuteFunction(function, *args, **kwargs)
1023+
9391024

9401025
if __name__ == '__main__':
9411026
unittest.main()

0 commit comments

Comments
 (0)