diff --git a/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs b/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
index 5366aca94b..cd582f0c18 100644
--- a/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
+++ b/Microsoft.Azure.Cosmos/src/ConnectionPolicy.cs
@@ -536,6 +536,15 @@ internal CosmosClientTelemetryOptions CosmosClientTelemetryOptions
set;
}
+ ///
+ /// provides SessionTokenMismatchRetryPolicy optimization through customer supplied region switch hints
+ ///
+ internal SessionRetryOptions SessionRetryOptions
+ {
+ get;
+ set;
+ }
+
///
/// GlobalEndpointManager will subscribe to this event if user updates the preferredLocations list in the Azure Cosmos DB service.
///
diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
index 770963efe8..7508bb1620 100644
--- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
+++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs
@@ -89,7 +89,8 @@ public CosmosClientOptions()
this.ConnectionProtocol = CosmosClientOptions.DefaultProtocol;
this.ApiType = CosmosClientOptions.DefaultApiType;
this.CustomHandlers = new Collection();
- this.CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions();
+ this.CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions();
+ this.SessionRetryOptions = new SessionRetryOptions();
}
///
@@ -120,7 +121,12 @@ public string ApplicationName
///
/// Get or set session container for the client
///
- internal ISessionContainer SessionContainer { get; set; }
+ internal ISessionContainer SessionContainer { get; set; }
+
+ ///
+ /// hint which guide SDK-internal retry policies on how early to switch retries to a different region.
+ ///
+ internal SessionRetryOptions SessionRetryOptions { get; private set; }
///
/// Gets or sets the location where the application is running. This will influence the SDK's choice for the Azure Cosmos DB service interaction.
@@ -740,6 +746,20 @@ public Func HttpClientFactory
/// after the threshold step time, the SDK will hedge to the third region and so on.
///
public AvailabilityStrategy AvailabilityStrategy { get; set; }
+
+ ///
+ /// provides SessionTokenMismatchRetryPolicy optimization through customer supplied region switch hints
+ ///
+#if PREVIEW
+ public
+#else
+ internal
+#endif
+ bool EnableRemoteRegionPreferredForSessionRetry
+ {
+ get => this.SessionRetryOptions.RemoteRegionPreferred;
+ set => this.SessionRetryOptions.RemoteRegionPreferred = value;
+ }
///
/// Enable partition key level failover
@@ -1004,7 +1024,8 @@ internal virtual ConnectionPolicy GetConnectionPolicy(int clientId)
ConnectionProtocol = this.ConnectionProtocol,
UserAgentContainer = this.CreateUserAgentContainerWithFeatures(clientId),
UseMultipleWriteLocations = true,
- IdleTcpConnectionTimeout = this.IdleTcpConnectionTimeout,
+ IdleTcpConnectionTimeout = this.IdleTcpConnectionTimeout,
+ SessionRetryOptions = this.SessionRetryOptions,
OpenTcpConnectionTimeout = this.OpenTcpConnectionTimeout,
MaxRequestsPerTcpConnection = this.MaxRequestsPerTcpConnection,
MaxTcpConnectionsPerEndpoint = this.MaxTcpConnectionsPerEndpoint,
diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs
index 7772dcd5a1..34fa32ae10 100644
--- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs
+++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs
@@ -6800,7 +6800,8 @@ private void CreateStoreModel(bool subscribeRntbdStatus)
!this.enableRntbdChannel,
this.UseMultipleWriteLocations && (this.accountServiceConfiguration.DefaultConsistencyLevel != Documents.ConsistencyLevel.Strong),
true,
- enableReplicaValidation: this.isReplicaAddressValidationEnabled);
+ enableReplicaValidation: this.isReplicaAddressValidationEnabled,
+ sessionRetryOptions: this.ConnectionPolicy.SessionRetryOptions);
if (subscribeRntbdStatus)
{
diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
index 6cd332c011..0f4adf9f2d 100644
--- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
+++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs
@@ -581,8 +581,24 @@ public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosm
{
this.clientOptions.SerializerOptions = cosmosSerializerOptions;
return this;
- }
-
+ }
+
+ ///
+ /// provides SessionTokenMismatchRetryPolicy optimization through customer supplied region switch hints
+ ///
+ ///
+ /// The object
+#if PREVIEW
+ public
+#else
+ internal
+#endif
+ CosmosClientBuilder WithEnableRemoteRegionPreferredForSessionRetry(bool enableRemoteRegionPreferredForSessionRetry)
+ {
+ this.clientOptions.EnableRemoteRegionPreferredForSessionRetry = enableRemoteRegionPreferredForSessionRetry;
+ return this;
+ }
+
///
/// Set a custom JSON serializer.
///
diff --git a/Microsoft.Azure.Cosmos/src/SessionRetryOptions.cs b/Microsoft.Azure.Cosmos/src/SessionRetryOptions.cs
new file mode 100644
index 0000000000..e233efaa97
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/src/SessionRetryOptions.cs
@@ -0,0 +1,43 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// ------------------------------------------------------------
+
+namespace Microsoft.Azure.Cosmos
+{
+ using System;
+ using Microsoft.Azure.Documents;
+
+ ///
+ /// Implementation of ISessionRetryOptions interface, do not want clients to subclass.
+ ///
+ internal sealed class SessionRetryOptions : ISessionRetryOptions
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public SessionRetryOptions()
+ {
+ this.MinInRegionRetryTime = ConfigurationManager.GetMinRetryTimeInLocalRegionWhenRemoteRegionPreferred();
+ this.MaxInRegionRetryCount = ConfigurationManager.GetMaxRetriesInLocalRegionWhenRemoteRegionPreferred();
+ }
+ ///
+ /// Sets the minimum retry time for 404/1002 retries within each region for read and write operations.
+ /// The minimum value is 100ms - this minimum is enforced to provide a way for the local region to catch-up on replication lag. The default value is 500ms - as a recommendation ensure that this value is higher than the steady-state
+ /// replication latency between the regions you chose
+ ///
+ public TimeSpan MinInRegionRetryTime { get; private set; }
+
+ ///
+ /// Sets the maximum number of retries within each region for read and write operations. The minimum value is 1 - the backoff time for the last in-region retry will ensure that the total retry time within the
+ /// region is at least the min. in-region retry time.
+ ///
+ public int MaxInRegionRetryCount { get; private set; }
+
+ ///
+ /// hints which guide SDK-internal retry policies on how early to switch retries to a different region. If true, will retry all replicas once and add a minimum delay before switching to the next region.If false, it will
+ /// retry in the local region up to 5s
+ ///
+ public bool RemoteRegionPreferred { get; set; } = false;
+
+ }
+}
\ No newline at end of file
diff --git a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
index a2d0f3652d..622f81c81b 100644
--- a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
+++ b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
@@ -72,6 +72,20 @@ internal static class ConfigurationManager
///
internal static readonly string DistributedQueryGatewayModeEnabled = "AZURE_COSMOS_DISTRIBUTED_QUERY_GATEWAY_ENABLED";
+ ///
+ /// intent is If a client specify a value, we will force it to be atleast 100ms, otherwise default is going to be 500ms
+ ///
+ internal static readonly string MinInRegionRetryTimeForWritesInMs = "AZURE_COSMOS_SESSION_TOKEN_MISMATCH_IN_REGION_RETRY_TIME_IN_MILLISECONDS";
+ internal static readonly int DefaultMinInRegionRetryTimeForWritesInMs = 500;
+ internal static readonly int MinMinInRegionRetryTimeForWritesInMs = 100;
+
+ ///
+ /// intent is If a client specify a value, we will force it to be atleast 1, otherwise default is going to be 1(right now both the values are 1 but we have the provision to change them in future).
+ ///
+ internal static readonly string MaxRetriesInLocalRegionWhenRemoteRegionPreferred = "AZURE_COSMOS_MAX_RETRIES_IN_LOCAL_REGION_WHEN_REMOTE_REGION_PREFERRED";
+ internal static readonly int DefaultMaxRetriesInLocalRegionWhenRemoteRegionPreferred = 1;
+ internal static readonly int MinMaxRetriesInLocalRegionWhenRemoteRegionPreferred = 1;
+
///
/// A read-only string containing the environment variable name for enabling binary encoding. This will eventually
/// be removed once binary encoding is enabled by default for both preview
@@ -96,6 +110,26 @@ public static T GetEnvironmentVariable(string variable, T defaultValue)
return (T)Convert.ChangeType(value, typeof(T));
}
+ public static int GetMaxRetriesInLocalRegionWhenRemoteRegionPreferred()
+ {
+ return Math.Max(
+ ConfigurationManager
+ .GetEnvironmentVariable(
+ variable: MaxRetriesInLocalRegionWhenRemoteRegionPreferred,
+ defaultValue: DefaultMaxRetriesInLocalRegionWhenRemoteRegionPreferred),
+ MinMaxRetriesInLocalRegionWhenRemoteRegionPreferred);
+ }
+
+ public static TimeSpan GetMinRetryTimeInLocalRegionWhenRemoteRegionPreferred()
+ {
+ return TimeSpan.FromMilliseconds(Math.Max(
+ ConfigurationManager
+ .GetEnvironmentVariable(
+ variable: MinInRegionRetryTimeForWritesInMs,
+ defaultValue: DefaultMinInRegionRetryTimeForWritesInMs),
+ MinMinInRegionRetryTimeForWritesInMs));
+ }
+
///
/// Gets the boolean value of the replica validation environment variable. Note that, replica validation
/// is enabled by default for the preview package and disabled for GA at the moment. The user can set the
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SessionRetryOptionsTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SessionRetryOptionsTest.cs
new file mode 100644
index 0000000000..4775d99322
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/SessionRetryOptionsTest.cs
@@ -0,0 +1,416 @@
+namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Data;
+ using System.Diagnostics;
+ using System.Linq;
+ using System.Net;
+ using System.Threading.Tasks;
+ using Microsoft.Azure.Cosmos;
+ using Microsoft.Azure.Cosmos.FaultInjection;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Container = Container;
+
+ [TestClass]
+ public class SessionRetryOptionsTest
+ {
+ private string connectionString;
+ private IDictionary writeRegionMap;
+
+ // to run code before running each test
+ [TestInitialize]
+ public async Task TestInitAsync()
+ {
+ this.connectionString = ConfigurationManager.GetEnvironmentVariable("COSMOSDB_MULTI_REGION", null);
+ if (string.IsNullOrEmpty(this.connectionString))
+ {
+ Assert.Fail("Set environment variable COSMOSDB_MULTI_REGION to run the tests");
+ }
+
+ CosmosClient client = new CosmosClient(this.connectionString);
+ await MultiRegionSetupHelpers.GetOrCreateMultiRegionDatabaseAndContainers(client);
+ this.writeRegionMap = client.DocumentClient.GlobalEndpointManager.GetAvailableWriteEndpointsByLocation();
+ Assert.IsTrue(this.writeRegionMap.Count() >= 2);
+
+ }
+ [TestMethod]
+ [DataRow(FaultInjectionOperationType.ReadItem, 2, true, DisplayName = "Validate Read Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.QueryItem, 1, true, DisplayName = "Validate Query Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.ReadItem, 2, false, DisplayName = "Validate Read Item operation with local region preferred.")]
+ [DataRow(FaultInjectionOperationType.QueryItem, 2, false, DisplayName = "Validate Query Item operation with local region preferred.")]
+ [TestCategory("MultiMaster")]
+ public async Task ReadOperationWithReadSessionUnavailableTest(FaultInjectionOperationType faultInjectionOperationType,
+ int sessionTokenMismatchRetryAttempts, Boolean remoteRegionPreferred)
+ {
+ string[] preferredRegions = this.writeRegionMap.Keys.ToArray();
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, "100");
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, Convert.ToString(sessionTokenMismatchRetryAttempts));
+ try
+ {
+ // if I go to first region for reading an item, I should get a 404/2002 response for 10 minutes
+ FaultInjectionRule badSessionTokenRule = new FaultInjectionRuleBuilder(
+ id: "badSessionTokenRule",
+ condition:
+ new FaultInjectionConditionBuilder()
+ .WithOperationType(faultInjectionOperationType)
+ .WithRegion(preferredRegions[0])
+ .Build(),
+ result:
+ FaultInjectionResultBuilder.GetResultBuilder(FaultInjectionServerErrorType.ReadSessionNotAvailable)
+ .Build())
+ .WithDuration(TimeSpan.FromMinutes(10))
+ .Build();
+
+ List rules = new List() { badSessionTokenRule };
+ FaultInjector faultInjector = new FaultInjector(rules);
+ Assert.IsNotNull(faultInjector);
+ CosmosClientOptions clientOptions = new CosmosClientOptions()
+ {
+ EnableRemoteRegionPreferredForSessionRetry = remoteRegionPreferred,
+ ConsistencyLevel = ConsistencyLevel.Session,
+ ApplicationPreferredRegions = preferredRegions,
+ ConnectionMode = ConnectionMode.Direct,
+ };
+
+ using (CosmosClient faultInjectionClient = new CosmosClient(
+ connectionString: this.connectionString,
+ clientOptions: faultInjector.GetFaultInjectionClientOptions(clientOptions)))
+ {
+ Database database = faultInjectionClient.GetDatabase(MultiRegionSetupHelpers.dbName);
+ Container container = await database.CreateContainerIfNotExistsAsync("sessionRetryPolicy", "/id");
+ string GUID = Guid.NewGuid().ToString();
+ dynamic testObject = new
+ {
+ id = GUID,
+ name = "customer one",
+ address = new
+ {
+ line1 = "45 new street",
+ city = "mckinney",
+ postalCode = "98989",
+ }
+
+ };
+
+ ItemResponse response = await container.CreateItemAsync(testObject);
+ Assert.IsNotNull(response);
+
+ OperationExecutionResult executionResult = await this.PerformDocumentOperation(faultInjectionOperationType, container, testObject);
+ this.ValidateOperationExecutionResult(executionResult, remoteRegionPreferred);
+
+ // For a non-write operation, the request can go to multiple replicas (upto 4 replicas)
+ // Check if the SessionTokenMismatchRetryPolicy retries on the bad / lagging region
+ // for sessionTokenMismatchRetryAttempts by tracking the badSessionTokenRule hit count
+ long hitCount = badSessionTokenRule.GetHitCount();
+
+ if (remoteRegionPreferred)
+ {
+ Assert.IsTrue(hitCount >= sessionTokenMismatchRetryAttempts && hitCount <= (1 + sessionTokenMismatchRetryAttempts) * 4);
+ }
+ }
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, null);
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, null);
+ }
+ }
+
+ [TestMethod]
+ [DataRow(FaultInjectionOperationType.CreateItem, 2, true, DisplayName = "Validate Write Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.ReplaceItem, 1, true, DisplayName = "Validate Replace Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.DeleteItem, 2, true, DisplayName = "Validate Delete Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.UpsertItem, 3, true, DisplayName = "Validate Upsert Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.PatchItem, 1, true, DisplayName = "Validate Patch Item operation with remote region preferred.")]
+ [DataRow(FaultInjectionOperationType.CreateItem, 3, false, DisplayName = "Validate Write Item operation with local region preferred.")]
+ [DataRow(FaultInjectionOperationType.ReplaceItem, 1, false, DisplayName = "Validate Replace Item operation with local region preferred.")]
+ [DataRow(FaultInjectionOperationType.DeleteItem, 2, false, DisplayName = "Validate Delete Item operation with local region preferred.")]
+ [DataRow(FaultInjectionOperationType.UpsertItem, 1, false, DisplayName = "Validate Upsert Item operation with local region preferred.")]
+ [DataRow(FaultInjectionOperationType.PatchItem, 1, false, DisplayName = "Validate Patch Item operation with remote region preferred.")]
+ [TestCategory("MultiMaster")]
+ public async Task WriteOperationWithReadSessionUnavailableTest(FaultInjectionOperationType faultInjectionOperationType,
+ int sessionTokenMismatchRetryAttempts, Boolean remoteRegionPreferred)
+ {
+
+ string[] preferredRegions = this.writeRegionMap.Keys.ToArray();
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, "100");
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, Convert.ToString(sessionTokenMismatchRetryAttempts));
+
+ try
+ {
+ FaultInjectionRule badSessionTokenRule = new FaultInjectionRuleBuilder(
+ id: "badSessionTokenRule",
+ condition:
+ new FaultInjectionConditionBuilder()
+ .WithOperationType(faultInjectionOperationType)
+ .WithRegion(preferredRegions[0])
+ .Build(),
+ result:
+ FaultInjectionResultBuilder.GetResultBuilder(FaultInjectionServerErrorType.ReadSessionNotAvailable)
+ .Build())
+ .WithDuration(TimeSpan.FromMinutes(10))
+ .Build();
+
+ List rules = new List() { badSessionTokenRule };
+ FaultInjector faultInjector = new FaultInjector(rules);
+
+ CosmosClientOptions clientOptions = new CosmosClientOptions()
+ {
+ EnableRemoteRegionPreferredForSessionRetry = remoteRegionPreferred,
+ ConsistencyLevel = ConsistencyLevel.Session,
+ ApplicationPreferredRegions = preferredRegions,
+ ConnectionMode = ConnectionMode.Direct,
+ };
+
+ using (CosmosClient faultInjectionClient = new CosmosClient(
+ connectionString: this.connectionString,
+ clientOptions: faultInjector.GetFaultInjectionClientOptions(clientOptions)))
+ {
+ Database database = faultInjectionClient.GetDatabase(MultiRegionSetupHelpers.dbName);
+ Container container = await database.CreateContainerIfNotExistsAsync("sessionRetryPolicy", "/id");
+ string GUID = Guid.NewGuid().ToString();
+ dynamic testObject = new
+ {
+ id = GUID,
+ name = "customer one",
+ address = new
+ {
+ line1 = "45 new street",
+ city = "mckinney",
+ postalCode = "98989",
+ }
+
+ };
+
+ OperationExecutionResult executionResult = await this.PerformDocumentOperation(faultInjectionOperationType, container, testObject);
+ this.ValidateOperationExecutionResult(executionResult, remoteRegionPreferred);
+
+ // For a write operation, the request can just go to the primary replica
+ // Check if the SessionTokenMismatchRetryPolicy retries on the bad / lagging region
+ // for sessionTokenMismatchRetryAttempts by tracking the badSessionTokenRule hit count
+ long hitCount = badSessionTokenRule.GetHitCount();
+ if (remoteRegionPreferred)
+ {
+ // higher hit count is possible while in MinRetryWaitTimeWithinRegion
+ Assert.IsTrue(hitCount >= sessionTokenMismatchRetryAttempts);
+ }
+ }
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, null);
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, null);
+ }
+ }
+
+ private void ValidateOperationExecutionResult(OperationExecutionResult operationExecutionResult, Boolean remoteRegionPreferred)
+ {
+ int sessionTokenMismatchDefaultWaitTime = 5000;
+
+ FaultInjectionOperationType executionOpType = operationExecutionResult.OperationType;
+ HttpStatusCode statusCode = operationExecutionResult.StatusCode;
+
+ int executionDuration = operationExecutionResult.Duration;
+ Trace.TraceInformation($" status code is {statusCode}");
+ Trace.TraceInformation($" execution duration is {executionDuration}");
+
+ if (executionOpType == FaultInjectionOperationType.CreateItem)
+ {
+ Assert.IsTrue(statusCode == HttpStatusCode.Created);
+ }
+ else if (executionOpType == FaultInjectionOperationType.DeleteItem)
+ {
+ Assert.IsTrue(statusCode == HttpStatusCode.NoContent);
+
+ }
+ else if (executionOpType == FaultInjectionOperationType.UpsertItem)
+ {
+ Assert.IsTrue(statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Created);
+
+ }
+ else
+ {
+ Assert.IsTrue(statusCode == HttpStatusCode.OK);
+ }
+
+ if (remoteRegionPreferred)
+ {
+ Assert.IsTrue(executionDuration < sessionTokenMismatchDefaultWaitTime);
+ }
+ else
+ {
+ Assert.IsTrue(executionDuration > sessionTokenMismatchDefaultWaitTime);
+ }
+ }
+
+
+ private async Task PerformDocumentOperation(FaultInjectionOperationType operationType, Container container,
+ dynamic testObject)
+ {
+
+ Stopwatch durationTimer = new Stopwatch();
+ if (operationType == FaultInjectionOperationType.ReadItem)
+ {
+ durationTimer.Start();
+ ItemResponse itemResponse = await container.ReadItemAsync(testObject.id,
+ new PartitionKey(testObject.id));
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+ return new OperationExecutionResult(
+ itemResponse.Diagnostics,
+ timeElapsed,
+ itemResponse.StatusCode,
+ operationType);
+
+ }
+
+ if (operationType == FaultInjectionOperationType.CreateItem)
+ {
+ durationTimer.Start();
+ ItemResponse itemResponse = await container.CreateItemAsync(testObject);
+
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+ return new OperationExecutionResult(
+ itemResponse.Diagnostics,
+ timeElapsed,
+ itemResponse.StatusCode,
+ operationType);
+
+ }
+
+ if (operationType == FaultInjectionOperationType.ReplaceItem)
+ {
+
+ await container.CreateItemAsync(testObject);
+ durationTimer.Start();
+
+ ItemResponse itemResponse = await container.ReplaceItemAsync(testObject, testObject.id, new PartitionKey(testObject.id));
+
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+ return new OperationExecutionResult(
+ itemResponse.Diagnostics,
+ timeElapsed,
+ itemResponse.StatusCode,
+ operationType);
+
+ }
+
+
+ if (operationType == FaultInjectionOperationType.UpsertItem)
+ {
+
+ durationTimer.Start();
+ ItemResponse itemResponse = await container.UpsertItemAsync(testObject, new PartitionKey(testObject.id));
+
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+ return new OperationExecutionResult(
+ itemResponse.Diagnostics,
+ timeElapsed,
+ itemResponse.StatusCode,
+ operationType);
+
+ }
+
+ if (operationType == FaultInjectionOperationType.DeleteItem)
+ {
+
+ await container.CreateItemAsync(testObject);
+
+ durationTimer.Start();
+ ItemResponse itemResponse = await container.DeleteItemAsync(testObject.id, new PartitionKey(testObject.id));
+
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+ return new OperationExecutionResult(
+ itemResponse.Diagnostics,
+ timeElapsed,
+ itemResponse.StatusCode,
+ operationType);
+
+ }
+
+ if (operationType == FaultInjectionOperationType.QueryItem)
+ {
+ durationTimer.Start();
+ String query = $"SELECT * from c where c.id = \"{testObject.id}\"";
+ FeedIterator feed = container.GetItemQueryIterator(query);
+ Assert.IsTrue(feed.HasMoreResults);
+ FeedResponse feedResponse = null;
+ while (feed.HasMoreResults)
+ {
+ feedResponse = await feed.ReadNextAsync();
+ Assert.IsNotNull(feedResponse);
+ Trace.TraceInformation($" feed response count is {feedResponse.Count}");
+ Assert.IsTrue(feedResponse.Count == 1);
+ }
+
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+ return new OperationExecutionResult(
+ feedResponse.Diagnostics,
+ timeElapsed,
+ feedResponse.StatusCode,
+ operationType);
+
+ }
+
+ if (operationType == FaultInjectionOperationType.PatchItem)
+ {
+ await container.CreateItemAsync(testObject);
+ durationTimer.Start();
+
+ ItemResponse itemResponse = await container.PatchItemAsync(testObject.id, new PartitionKey(testObject.id),
+ patchOperations: new[]
+ {
+ PatchOperation.Replace("/name", "Customer Two")
+ });
+
+ durationTimer.Stop();
+ int timeElapsed = Convert.ToInt32(durationTimer.Elapsed.TotalMilliseconds);
+
+
+
+ return new OperationExecutionResult(
+ itemResponse.Diagnostics,
+ timeElapsed,
+ itemResponse.StatusCode,
+ operationType);
+
+ }
+
+
+
+ return null;
+ }
+
+ }
+
+ internal class OperationExecutionResult
+ {
+ public CosmosDiagnostics Diagnostics { get; set; }
+ public int Duration { get; set; }
+ public HttpStatusCode StatusCode { get; set; }
+ public FaultInjectionOperationType OperationType { get; set; }
+
+ public OperationExecutionResult(CosmosDiagnostics diagnostics, int duration, HttpStatusCode statusCode, FaultInjectionOperationType operationType)
+ {
+ this.Diagnostics = diagnostics;
+ this.Duration = duration;
+ this.StatusCode = statusCode;
+ this.OperationType = operationType;
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json
index 268a52d9f5..926221f2f3 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json
@@ -321,6 +321,16 @@
"Microsoft.Azure.Cosmos.CosmosClientOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
+ "Boolean EnableRemoteRegionPreferredForSessionRetry": {
+ "Type": "Property",
+ "Attributes": [],
+ "MethodInfo": "Boolean EnableRemoteRegionPreferredForSessionRetry;CanRead:True;CanWrite:True;Boolean get_EnableRemoteRegionPreferredForSessionRetry();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_EnableRemoteRegionPreferredForSessionRetry(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ },
+ "Boolean get_EnableRemoteRegionPreferredForSessionRetry()": {
+ "Type": "Method",
+ "Attributes": [],
+ "MethodInfo": "Boolean get_EnableRemoteRegionPreferredForSessionRetry();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ },
"System.Nullable`1[System.Int32] get_ThroughputBucket()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": {
"Type": "Method",
"Attributes": [
@@ -333,6 +343,11 @@
"Attributes": [],
"MethodInfo": "System.Nullable`1[System.Int32] ThroughputBucket;CanRead:True;CanWrite:True;System.Nullable`1[System.Int32] get_ThroughputBucket();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_ThroughputBucket(System.Nullable`1[System.Int32]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
},
+ "Void set_EnableRemoteRegionPreferredForSessionRetry(Boolean)": {
+ "Type": "Method",
+ "Attributes": [],
+ "MethodInfo": "Void set_EnableRemoteRegionPreferredForSessionRetry(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ },
"Void set_ThroughputBucket(System.Nullable`1[System.Int32])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": {
"Type": "Method",
"Attributes": [
@@ -1161,6 +1176,11 @@
"Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": {
"Subclasses": {},
"Members": {
+ "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithEnableRemoteRegionPreferredForSessionRetry(Boolean)": {
+ "Type": "Method",
+ "Attributes": [],
+ "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithEnableRemoteRegionPreferredForSessionRetry(Boolean);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;"
+ },
"Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithThroughputBucket(Int32)": {
"Type": "Method",
"Attributes": [],
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SessionRetryOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SessionRetryOptionsUnitTests.cs
new file mode 100644
index 0000000000..a441ec0669
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/SessionRetryOptionsUnitTests.cs
@@ -0,0 +1,76 @@
+namespace Microsoft.Azure.Cosmos.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ ///
+ /// Tests for
+ ///
+ [TestClass]
+ public class SessionRetryOptionsUnitTests
+ {
+ [TestMethod]
+ public void SessionRetryOptionsValidValuesTest()
+ {
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, "200");
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, "1");
+ try
+ {
+ CosmosClientOptions clientOptions = new CosmosClientOptions()
+ {
+ EnableRemoteRegionPreferredForSessionRetry = true,
+ };
+
+ Assert.IsTrue(clientOptions.SessionRetryOptions.MinInRegionRetryTime == TimeSpan.FromMilliseconds(200));
+ Assert.IsTrue(clientOptions.SessionRetryOptions.MaxInRegionRetryCount == 1);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, null);
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, null);
+ }
+
+ }
+
+ [TestMethod]
+ public void SessionRetryOptionsDefaultValuesTest()
+ {
+ CosmosClientOptions clientOptions = new CosmosClientOptions()
+ {
+ EnableRemoteRegionPreferredForSessionRetry = true,
+ };
+
+ Assert.IsTrue(clientOptions.SessionRetryOptions.MinInRegionRetryTime == TimeSpan.FromMilliseconds(500));
+ Assert.IsTrue(clientOptions.SessionRetryOptions.MaxInRegionRetryCount == 1);
+
+ }
+
+ [TestMethod]
+ public void SessionRetryOptionsInValidValuesTest()
+ {
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, "50");
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, "0");
+ try
+ {
+ CosmosClientOptions clientOptions = new CosmosClientOptions()
+ {
+ EnableRemoteRegionPreferredForSessionRetry = true,
+ };
+
+ Assert.IsTrue(clientOptions.SessionRetryOptions.MinInRegionRetryTime == TimeSpan.FromMilliseconds(100));
+ Assert.IsTrue(clientOptions.SessionRetryOptions.MaxInRegionRetryCount == 1);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(ConfigurationManager.MinInRegionRetryTimeForWritesInMs, null);
+ Environment.SetEnvironmentVariable(ConfigurationManager.MaxRetriesInLocalRegionWhenRemoteRegionPreferred, null);
+ }
+
+ }
+
+ }
+}
\ No newline at end of file