Skip to content

Commit b6ec4be

Browse files
dibahlfiaavasthykirankumarkolliNaluTripician
authored
CrossRegionHedging: Fixes NullReference Exception Bug (#4869)
# Pull Request Template ## Description This change is to fix CosmosNullReferenceException that happens when a client passes a CancellationToken along with the request. Please see(#4737) for more information. ## Type of change Please delete options that are not relevant. - [] Bug fix (non-breaking change which fixes an issue) ## Closing issues To automatically close an issue: closes #4737 --------- Co-authored-by: Arooshi Avasthy <[email protected]> Co-authored-by: Kiran Kumar Kolli <[email protected]> Co-authored-by: Nalu Tripician <[email protected]>
1 parent 80d9e2e commit b6ec4be

File tree

3 files changed

+101
-6
lines changed

3 files changed

+101
-6
lines changed

Microsoft.Azure.Cosmos/src/Routing/AvailabilityStrategy/CrossRegionHedgingAvailabilityStrategy.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ internal override async Task<ResponseMessage> ExecuteAvailabilityStrategyAsync(
160160
request,
161161
hedgeRegions.ElementAt(requestNumber),
162162
cancellationToken,
163-
cancellationTokenSource);
163+
cancellationTokenSource,
164+
trace);
164165

165166
requestTasks.Add(primaryRequest);
166167
}
@@ -279,7 +280,8 @@ private async Task<HedgingResponse> CloneAndSendAsync(
279280
clonedRequest,
280281
region,
281282
cancellationToken,
282-
cancellationTokenSource);
283+
cancellationTokenSource,
284+
trace);
283285
}
284286
}
285287

@@ -288,7 +290,8 @@ private async Task<HedgingResponse> RequestSenderAndResultCheckAsync(
288290
RequestMessage request,
289291
string hedgedRegion,
290292
CancellationToken cancellationToken,
291-
CancellationTokenSource cancellationTokenSource)
293+
CancellationTokenSource cancellationTokenSource,
294+
ITrace trace)
292295
{
293296
try
294297
{
@@ -305,9 +308,9 @@ private async Task<HedgingResponse> RequestSenderAndResultCheckAsync(
305308

306309
return new HedgingResponse(false, response, hedgedRegion);
307310
}
308-
catch (OperationCanceledException) when (cancellationTokenSource.IsCancellationRequested)
311+
catch (OperationCanceledException oce ) when (cancellationTokenSource.IsCancellationRequested)
309312
{
310-
return new HedgingResponse(false, null, hedgedRegion);
313+
throw new CosmosOperationCanceledException(oce, trace);
311314
}
312315
catch (Exception ex)
313316
{

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAvailabilityStrategyTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,60 @@ await this.container.DeleteItemAsync<AvailabilityStrategyTestObject>(
12191219
}
12201220
}
12211221

1222+
[TestMethod]
1223+
[TestCategory("MultiRegion")]
1224+
public async Task AvailabilityStrategyWithCancellationTokenThrowsExceptionTest()
1225+
{
1226+
FaultInjectionRule responseDelay = new FaultInjectionRuleBuilder(
1227+
id: "responseDely",
1228+
condition:
1229+
new FaultInjectionConditionBuilder()
1230+
.WithRegion(region1)
1231+
.WithOperationType(FaultInjectionOperationType.ReadItem)
1232+
.Build(),
1233+
result:
1234+
FaultInjectionResultBuilder.GetResultBuilder(FaultInjectionServerErrorType.ResponseDelay)
1235+
.WithDelay(TimeSpan.FromMilliseconds(6000))
1236+
.Build())
1237+
.WithDuration(TimeSpan.FromMinutes(90))
1238+
.WithHitLimit(2)
1239+
.Build();
1240+
1241+
List<FaultInjectionRule> rules = new List<FaultInjectionRule>() { responseDelay };
1242+
FaultInjector faultInjector = new FaultInjector(rules);
1243+
1244+
responseDelay.Disable();
1245+
1246+
CosmosClientOptions clientOptions = new CosmosClientOptions()
1247+
{
1248+
ConnectionMode = ConnectionMode.Direct,
1249+
ApplicationPreferredRegions = new List<string>() { region1, region2 },
1250+
AvailabilityStrategy = AvailabilityStrategy.CrossRegionHedgingStrategy(
1251+
threshold: TimeSpan.FromMilliseconds(300),
1252+
thresholdStep: null),
1253+
Serializer = this.cosmosSystemTextJsonSerializer
1254+
};
1255+
1256+
using (CosmosClient faultInjectionClient = new CosmosClient(
1257+
connectionString: this.connectionString,
1258+
clientOptions: faultInjector.GetFaultInjectionClientOptions(clientOptions)))
1259+
{
1260+
CancellationTokenSource cts = new CancellationTokenSource();
1261+
cts.Cancel();
1262+
1263+
Database database = faultInjectionClient.GetDatabase(CosmosAvailabilityStrategyTests.dbName);
1264+
Container container = database.GetContainer(CosmosAvailabilityStrategyTests.containerName);
1265+
1266+
CosmosOperationCanceledException cancelledException = await Assert.ThrowsExceptionAsync<CosmosOperationCanceledException>(() =>
1267+
container.ReadItemAsync<AvailabilityStrategyTestObject>(
1268+
"testId",
1269+
new PartitionKey("pk"), cancellationToken: cts.Token
1270+
));
1271+
1272+
}
1273+
1274+
}
1275+
12221276
private static async Task HandleChangesAsync(
12231277
ChangeFeedProcessorContext context,
12241278
IReadOnlyCollection<AvailabilityStrategyTestObject> changes,

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/AvailabilityStrategyUnitTests.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
namespace Microsoft.Azure.Cosmos.Tests
22
{
33
using System;
4-
using System.Collections.Concurrent;
54
using System.Collections.Generic;
5+
using System.Collections.ObjectModel;
66
using System.IO;
77
using System.Net.Http;
8+
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.Azure.Cosmos;
1011
using Microsoft.Azure.Documents;
@@ -58,5 +59,42 @@ public async Task RequestMessageCloneTests()
5859
Assert.AreEqual(httpRequest.DatabaseId, clone.DatabaseId);
5960
}
6061
}
62+
63+
[TestMethod]
64+
public async Task CancellationTokenThrowsExceptionTest()
65+
{
66+
//Arrange
67+
CrossRegionHedgingAvailabilityStrategy availabilityStrategy = new CrossRegionHedgingAvailabilityStrategy(
68+
threshold: TimeSpan.FromMilliseconds(100),
69+
thresholdStep: TimeSpan.FromMilliseconds(50));
70+
71+
RequestMessage request = new RequestMessage
72+
{
73+
ResourceType = ResourceType.Document,
74+
OperationType = OperationType.Read
75+
};
76+
77+
CancellationTokenSource cts = new CancellationTokenSource();
78+
cts.Cancel();
79+
80+
AccountProperties databaseAccount = new AccountProperties()
81+
{
82+
ReadLocationsInternal = new Collection<AccountRegion>()
83+
{
84+
{ new AccountRegion() { Name = "US East", Endpoint = new Uri("https://location1.documents.azure.com").ToString() } },
85+
{ new AccountRegion() { Name = "US West", Endpoint = new Uri("https://location2.documents.azure.com").ToString() } },
86+
87+
}
88+
};
89+
using CosmosClient mockCosmosClient = MockCosmosUtil.CreateMockCosmosClient();
90+
mockCosmosClient.DocumentClient.GlobalEndpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(databaseAccount);
91+
92+
Func<RequestMessage, CancellationToken, Task<ResponseMessage>> sender = (request, token) => throw new OperationCanceledException("operation cancellation requested");
93+
94+
CosmosOperationCanceledException cancelledException = await Assert.ThrowsExceptionAsync<CosmosOperationCanceledException>(() =>
95+
availabilityStrategy.ExecuteAvailabilityStrategyAsync(sender, mockCosmosClient, request, cts.Token));
96+
}
97+
98+
6199
}
62100
}

0 commit comments

Comments
 (0)