diff --git a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs
index f3141bd675..37805d5b34 100644
--- a/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs
+++ b/Microsoft.Azure.Cosmos/src/Handler/RequestInvokerHandler.cs
@@ -509,6 +509,9 @@ private async Task ValidateAndSetConsistencyLevelAsync(RequestMessage requestMes
///
/// Validate and set the ReadConsistencyStrategy header.
+ /// When the strategy is LastCommittedWriteRegion and the operation is a read,
+ /// also set the hub region processing header so the backend routes the request
+ /// to the hub (write) region.
///
private Task ValidateAndSetReadConsistencyStrategyAsync(RequestMessage requestMessage)
{
@@ -529,6 +532,14 @@ private Task ValidateAndSetReadConsistencyStrategyAsync(RequestMessage requestMe
requestMessage.Headers.Set(
HttpConstants.HttpHeaders.ReadConsistencyStrategy,
readConsistencyStrategy.Value.ToString());
+
+ if (readConsistencyStrategy.Value == Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion
+ && OperationTypeExtensions.IsReadOperation(requestMessage.OperationType))
+ {
+ requestMessage.Headers.Set(
+ HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion,
+ bool.TrueString);
+ }
}
return Task.CompletedTask;
diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ReadConsistencyStrategy.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ReadConsistencyStrategy.cs
index 943492369d..c81a4a3342 100644
--- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ReadConsistencyStrategy.cs
+++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ReadConsistencyStrategy.cs
@@ -48,6 +48,12 @@ enum ReadConsistencyStrategy
/// Quorum read with GCLSN barrier - returns the latest version across all regions.
/// Only valid for accounts configured with Strong consistency.
///
- GlobalStrong = 4
+ GlobalStrong = 4,
+
+ ///
+ /// Returns the latest committed version from the hub (write) region, ensuring reads
+ /// reflect the most recent writes regardless of which region the client is connected to.
+ ///
+ LastCommittedWriteRegion = 5
}
}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs
index 5b01f886c3..7e0a5dd4e3 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs
@@ -41,7 +41,6 @@ public class CosmosItemTests : BaseCosmosClientHelper
private Container Container = null;
private ContainerProperties containerSettings = null;
- private const string HubRegionHeader = "x-ms-cosmos-hub-region-processing-only";
private static readonly string nonPartitionItemId = "fixed-Container-Item";
private static readonly string undefinedPartitionItemId = "undefined-partition-Item";
@@ -4340,7 +4339,7 @@ public async Task ReadItemAsync_ShouldAddHubHeader_OnRetryAfter_404_1002()
// Header should NOT be present on first retry (2nd request)
if (requestCount == 2 &&
- request.Headers.TryGetValues(HubRegionHeader, out IEnumerable firstRetryValues) &&
+ request.Headers.TryGetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion, out IEnumerable firstRetryValues) &&
firstRetryValues.Any())
{
Assert.Fail("Header should NOT be present on first retry attempt.");
@@ -4422,6 +4421,328 @@ public async Task ReadItemAsync_ShouldAddHubHeader_OnRetryAfter_404_1002()
Assert.AreEqual(2, return404Count, "Both requests should have returned 404/1002");
}
+ ///
+ /// Verifies that when ReadConsistencyStrategy.LastCommittedWriteRegion is set on
+ /// ItemRequestOptions, the hub region header and ReadConsistencyStrategy header are
+ /// present from the very first request and persist through all 403/3 retries.
+ /// Only LastCommittedWriteRegion triggers the hub header on reads.
+ /// Parameterized over forbidden count to cover single and multiple 403/3 retries.
+ ///
+ [TestMethod]
+ [Owner("aavasthy")]
+ [DataRow(1, DisplayName = "LastCommittedWriteRegion request-level: single 403/3 → success")]
+ [DataRow(3, DisplayName = "LastCommittedWriteRegion request-level: multiple 403/3 → success")]
+ [Description("Request-level LastCommittedWriteRegion sets hub header on reads with 403/3 retry flow.")]
+ public async Task ReadItemAsync_WithLastCommittedWriteRegion_RequestLevel_403_3_ThenSuccess(int forbiddenCount)
+ {
+ int docReadRequestCount = 0;
+ int return403Count = 0;
+ List hubHeaderPerRequest = new List();
+ List rcsHeaderPerRequest = new List();
+
+ HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
+ {
+ RequestCallBack = (request, cancellationToken) =>
+ {
+ if (request.Method == HttpMethod.Get
+ && request.RequestUri != null
+ && request.RequestUri.AbsolutePath.Contains("/docs/"))
+ {
+ docReadRequestCount++;
+
+ bool hasHubHeader = request.Headers.TryGetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion, out IEnumerable values)
+ && values.Any();
+ hubHeaderPerRequest.Add(hasHubHeader);
+
+ string rcsValue = null;
+ if (request.Headers.TryGetValues("x-ms-cosmos-read-consistency-strategy", out IEnumerable rcsValues))
+ {
+ rcsValue = rcsValues.FirstOrDefault();
+ }
+ rcsHeaderPerRequest.Add(rcsValue);
+
+ if (return403Count < forbiddenCount)
+ {
+ return403Count++;
+
+ HttpResponseMessage forbiddenResponse = new HttpResponseMessage(HttpStatusCode.Forbidden)
+ {
+ Content = new StringContent(
+ JsonConvert.SerializeObject(new { code = "Forbidden", message = "Simulated 403/3 WriteForbidden - not hub region" }),
+ Encoding.UTF8,
+ "application/json")
+ };
+ forbiddenResponse.Headers.Add("x-ms-substatus", ((int)SubStatusCodes.WriteForbidden).ToString());
+ forbiddenResponse.Headers.Add("x-ms-activity-id", Guid.NewGuid().ToString());
+ forbiddenResponse.Headers.Add("x-ms-request-charge", "1.0");
+
+ return Task.FromResult(forbiddenResponse);
+ }
+ }
+
+ return Task.FromResult(null);
+ }
+ };
+
+ CosmosClientOptions clientOptions = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(httpHandler),
+ };
+
+ using CosmosClient customClient = TestCommon.CreateCosmosClient(clientOptions);
+ Container customContainer = customClient.GetContainer(this.database.Id, this.Container.Id);
+
+ ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
+ await this.Container.CreateItemAsync(testItem, new Cosmos.PartitionKey(testItem.pk));
+
+ ItemResponse response = await customContainer.ReadItemAsync(
+ testItem.id,
+ new Cosmos.PartitionKey(testItem.pk),
+ new ItemRequestOptions { ReadConsistencyStrategy = Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion });
+
+ Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
+ Assert.IsNotNull(response.Resource);
+ Assert.AreEqual(testItem.id, response.Resource.id);
+
+ Assert.AreEqual(forbiddenCount, return403Count, $"Should have returned 403/3 exactly {forbiddenCount} time(s).");
+ Assert.IsTrue(docReadRequestCount >= forbiddenCount + 1,
+ $"Expected at least {forbiddenCount + 1} requests ({forbiddenCount}x 403/3 + 1x success), got {docReadRequestCount}.");
+
+ for (int i = 0; i < hubHeaderPerRequest.Count; i++)
+ {
+ Assert.IsTrue(hubHeaderPerRequest[i],
+ $"Hub region header MUST be present on request #{i + 1} for LastCommittedWriteRegion read.");
+ Assert.AreEqual("LastCommittedWriteRegion", rcsHeaderPerRequest[i],
+ $"ReadConsistencyStrategy header MUST be 'LastCommittedWriteRegion' on request #{i + 1}.");
+ }
+ }
+
+ ///
+ /// Verifies that when ReadConsistencyStrategy.LastCommittedWriteRegion is set at the
+ /// CosmosClientOptions level, the hub region header is present on the first read request
+ /// and the 403/3 retry flow succeeds.
+ ///
+ [TestMethod]
+ [Owner("aavasthy")]
+ [Description("Client-level LastCommittedWriteRegion sets hub header on reads with 403/3 then success.")]
+ public async Task ReadItemAsync_WithLastCommittedWriteRegion_ClientLevel_403_3_ThenSuccess()
+ {
+ int docReadRequestCount = 0;
+ int return403Count = 0;
+ const int maxReturn403 = 1;
+ List hubHeaderPerRequest = new List();
+ List rcsHeaderPerRequest = new List();
+
+ HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
+ {
+ RequestCallBack = (request, cancellationToken) =>
+ {
+ if (request.Method == HttpMethod.Get
+ && request.RequestUri != null
+ && request.RequestUri.AbsolutePath.Contains("/docs/"))
+ {
+ docReadRequestCount++;
+
+ bool hasHubHeader = request.Headers.TryGetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion, out IEnumerable values)
+ && values.Any();
+ hubHeaderPerRequest.Add(hasHubHeader);
+
+ string rcsValue = null;
+ if (request.Headers.TryGetValues("x-ms-cosmos-read-consistency-strategy", out IEnumerable rcsValues))
+ {
+ rcsValue = rcsValues.FirstOrDefault();
+ }
+ rcsHeaderPerRequest.Add(rcsValue);
+
+ if (return403Count < maxReturn403)
+ {
+ return403Count++;
+
+ HttpResponseMessage forbiddenResponse = new HttpResponseMessage(HttpStatusCode.Forbidden)
+ {
+ Content = new StringContent(
+ JsonConvert.SerializeObject(new { code = "Forbidden", message = "Simulated 403/3" }),
+ Encoding.UTF8,
+ "application/json")
+ };
+ forbiddenResponse.Headers.Add("x-ms-substatus", ((int)SubStatusCodes.WriteForbidden).ToString());
+ forbiddenResponse.Headers.Add("x-ms-activity-id", Guid.NewGuid().ToString());
+ forbiddenResponse.Headers.Add("x-ms-request-charge", "1.0");
+
+ return Task.FromResult(forbiddenResponse);
+ }
+ }
+
+ return Task.FromResult(null);
+ }
+ };
+
+ CosmosClientOptions clientOptions = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(httpHandler),
+ ReadConsistencyStrategy = Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion
+ };
+
+ using CosmosClient customClient = TestCommon.CreateCosmosClient(clientOptions);
+ Container customContainer = customClient.GetContainer(this.database.Id, this.Container.Id);
+
+ ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
+ await this.Container.CreateItemAsync(testItem, new Cosmos.PartitionKey(testItem.pk));
+
+ // Read without per-request options — client-level strategy should take effect
+ ItemResponse response = await customContainer.ReadItemAsync(
+ testItem.id,
+ new Cosmos.PartitionKey(testItem.pk));
+
+ Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
+ Assert.AreEqual(testItem.id, response.Resource.id);
+ Assert.AreEqual(maxReturn403, return403Count, "Should have returned 403/3 exactly once.");
+ Assert.IsTrue(docReadRequestCount >= 2, $"Expected at least 2 requests, got {docReadRequestCount}.");
+
+ for (int i = 0; i < hubHeaderPerRequest.Count; i++)
+ {
+ Assert.IsTrue(hubHeaderPerRequest[i],
+ $"Hub region header MUST be present on request #{i + 1} for client-level LastCommittedWriteRegion.");
+ Assert.AreEqual("LastCommittedWriteRegion", rcsHeaderPerRequest[i],
+ $"ReadConsistencyStrategy header MUST be 'LastCommittedWriteRegion' on request #{i + 1}.");
+ }
+ }
+
+ ///
+ /// Verifies that non-LastCommittedWriteRegion strategies (e.g. Session, Eventual)
+ /// set the ReadConsistencyStrategy header but do NOT set the hub region header.
+ /// This is the key behavioral distinction: only LastCommittedWriteRegion triggers
+ /// hub region routing.
+ ///
+ [TestMethod]
+ [Owner("aavasthy")]
+ [DataRow("Session", DisplayName = "Session strategy: RCS header present, no hub header")]
+ [DataRow("Eventual", DisplayName = "Eventual strategy: RCS header present, no hub header")]
+ [DataRow("LatestCommitted", DisplayName = "LatestCommitted strategy: RCS header present, no hub header")]
+ [Description("Non-LastCommittedWriteRegion strategies set RCS header but NOT hub region header.")]
+ public async Task ReadItemAsync_WithNonLastCommittedWriteRegionStrategy_NoHubHeader(string strategyName)
+ {
+ Cosmos.ReadConsistencyStrategy strategy =
+ (Cosmos.ReadConsistencyStrategy)Enum.Parse(typeof(Cosmos.ReadConsistencyStrategy), strategyName);
+
+ bool hubHeaderOnFirstRequest = false;
+ string rcsHeaderValue = null;
+ bool interceptedFirstRequest = false;
+
+ HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
+ {
+ RequestCallBack = (request, cancellationToken) =>
+ {
+ if (!interceptedFirstRequest
+ && request.Method == HttpMethod.Get
+ && request.RequestUri != null
+ && request.RequestUri.AbsolutePath.Contains("/docs/"))
+ {
+ interceptedFirstRequest = true;
+
+ hubHeaderOnFirstRequest = request.Headers.TryGetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion, out IEnumerable values)
+ && values.Any();
+
+ if (request.Headers.TryGetValues("x-ms-cosmos-read-consistency-strategy", out IEnumerable rcsValues))
+ {
+ rcsHeaderValue = rcsValues.FirstOrDefault();
+ }
+ }
+
+ return Task.FromResult(null);
+ }
+ };
+
+ CosmosClientOptions clientOptions = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(httpHandler),
+ };
+
+ using CosmosClient customClient = TestCommon.CreateCosmosClient(clientOptions);
+ Container customContainer = customClient.GetContainer(this.database.Id, this.Container.Id);
+
+ ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
+ await this.Container.CreateItemAsync(testItem, new Cosmos.PartitionKey(testItem.pk));
+
+ ItemResponse response = await customContainer.ReadItemAsync(
+ testItem.id,
+ new Cosmos.PartitionKey(testItem.pk),
+ new ItemRequestOptions { ReadConsistencyStrategy = strategy });
+
+ Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
+ Assert.IsTrue(interceptedFirstRequest, "Should have intercepted at least one doc read request.");
+ Assert.IsFalse(hubHeaderOnFirstRequest,
+ $"Hub region header should NOT be present for {strategyName} strategy — only LastCommittedWriteRegion triggers it.");
+ Assert.AreEqual(strategyName, rcsHeaderValue,
+ $"ReadConsistencyStrategy header should be '{strategyName}'.");
+ }
+
+ ///
+ /// Verifies that when ReadConsistencyStrategy is NOT set at all, neither the hub header
+ /// nor the ReadConsistencyStrategy header is present on the request.
+ ///
+ [TestMethod]
+ [Owner("aavasthy")]
+ [Description("Without ReadConsistencyStrategy, neither hub header nor RCS header is present.")]
+ public async Task ReadItemAsync_WithoutReadConsistencyStrategy_NoHeaders()
+ {
+ bool hubHeaderOnFirstRequest = false;
+ bool rcsHeaderOnFirstRequest = false;
+ bool interceptedFirstRequest = false;
+
+ HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
+ {
+ RequestCallBack = (request, cancellationToken) =>
+ {
+ if (!interceptedFirstRequest
+ && request.Method == HttpMethod.Get
+ && request.RequestUri != null
+ && request.RequestUri.AbsolutePath.Contains("/docs/"))
+ {
+ interceptedFirstRequest = true;
+
+ hubHeaderOnFirstRequest = request.Headers.TryGetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion, out IEnumerable values)
+ && values.Any();
+
+ rcsHeaderOnFirstRequest = request.Headers.TryGetValues("x-ms-cosmos-read-consistency-strategy", out IEnumerable rcsValues)
+ && rcsValues.Any();
+ }
+
+ return Task.FromResult(null);
+ }
+ };
+
+ CosmosClientOptions clientOptions = new CosmosClientOptions
+ {
+ ConnectionMode = ConnectionMode.Gateway,
+ ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
+ HttpClientFactory = () => new HttpClient(httpHandler),
+ };
+
+ using CosmosClient customClient = TestCommon.CreateCosmosClient(clientOptions);
+ Container customContainer = customClient.GetContainer(this.database.Id, this.Container.Id);
+
+ ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
+ await this.Container.CreateItemAsync(testItem, new Cosmos.PartitionKey(testItem.pk));
+
+ ItemResponse response = await customContainer.ReadItemAsync(
+ testItem.id,
+ new Cosmos.PartitionKey(testItem.pk));
+
+ Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
+ Assert.IsTrue(interceptedFirstRequest, "Should have intercepted at least one doc read request.");
+ Assert.IsFalse(hubHeaderOnFirstRequest,
+ "Hub region header should NOT be present when ReadConsistencyStrategy is not set.");
+ Assert.IsFalse(rcsHeaderOnFirstRequest,
+ "ReadConsistencyStrategy header should NOT be present when ReadConsistencyStrategy is not set.");
+ }
+
private async Task AutoGenerateIdPatternTest(Cosmos.PartitionKey pk, T itemWithoutId)
{
string autoId = Guid.NewGuid().ToString();
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs
index b595711070..3e0ec7cb84 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ClientRetryPolicyTests.cs
@@ -28,7 +28,6 @@ public sealed class ClientRetryPolicyTests
private static Uri Location1Endpoint = new Uri("https://location1.documents.azure.com");
private static Uri Location2Endpoint = new Uri("https://location2.documents.azure.com");
- private const string HubRegionHeader = "x-ms-cosmos-hub-region-processing-only";
private ReadOnlyCollection preferredLocations;
private AccountProperties databaseAccount;
private GlobalPartitionEndpointManager partitionKeyRangeLocationCache;
@@ -434,7 +433,7 @@ public async Task ClientRetryPolicy_HubRegionHeader_AddedOn404_1002_BasedOnAccou
// First attempt - header should not exist
retryPolicy.OnBeforeSendRequest(request);
- Assert.IsNull(request.Headers.GetValues(HubRegionHeader), "Header should not exist on initial request before any 404/1002 error.");
+ Assert.IsNull(request.Headers.GetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion), "Header should not exist on initial request before any 404/1002 error.");
// Simulate first 404/1002 error
DocumentClientException sessionNotAvailableException = new DocumentClientException(
@@ -450,7 +449,7 @@ public async Task ClientRetryPolicy_HubRegionHeader_AddedOn404_1002_BasedOnAccou
// First retry attempt - header should NOT be present yet
retryPolicy.OnBeforeSendRequest(request);
- string[] headerValues = request.Headers.GetValues(HubRegionHeader);
+ string[] headerValues = request.Headers.GetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion);
Assert.IsNull(headerValues, "Header should NOT be present on first retry attempt (before it fails).");
// Simulate first retry also failing with 404/1002
@@ -498,7 +497,7 @@ public async Task ClientRetryPolicy_HubRegionHeader_AddedOn404_1002_BasedOnAccou
{
// Now verify the header is present on this retry triggered by 503
retryPolicy.OnBeforeSendRequest(request);
- headerValues = request.Headers.GetValues(HubRegionHeader);
+ headerValues = request.Headers.GetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion);
Assert.IsNotNull(headerValues, "Header should be present on retry after 404/1002 flag was set.");
Assert.AreEqual(1, headerValues.Length, "Header should have exactly one value.");
Assert.AreEqual(bool.TrueString, headerValues[0], "Header value should be 'True'.");
@@ -512,7 +511,7 @@ public async Task ClientRetryPolicy_HubRegionHeader_AddedOn404_1002_BasedOnAccou
if (shouldRetry.ShouldRetry)
{
retryPolicy.OnBeforeSendRequest(request);
- headerValues = request.Headers.GetValues(HubRegionHeader);
+ headerValues = request.Headers.GetValues(HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion);
Assert.IsNull(headerValues, $"Header should NOT be present on retry attempt {retryAttempt} for multi-master account.");
// Simulate another 404/1002 or 503 to continue retry loop
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json
index 9a78dbe18a..02f1cf4648 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.net6.json
@@ -1491,6 +1491,11 @@
"Attributes": [],
"MethodInfo": "Microsoft.Azure.Cosmos.ReadConsistencyStrategy GlobalStrong;IsInitOnly:False;IsStatic:True;"
},
+ "Microsoft.Azure.Cosmos.ReadConsistencyStrategy LastCommittedWriteRegion": {
+ "Type": "Field",
+ "Attributes": [],
+ "MethodInfo": "Microsoft.Azure.Cosmos.ReadConsistencyStrategy LastCommittedWriteRegion;IsInitOnly:False;IsStatic:True;"
+ },
"Microsoft.Azure.Cosmos.ReadConsistencyStrategy LatestCommitted": {
"Type": "Field",
"Attributes": [],
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs
index 219260a587..5b09b9e189 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs
@@ -827,6 +827,7 @@ public int Compare(object x, object y)
[DataRow("Session")]
[DataRow("LatestCommitted")]
[DataRow("GlobalStrong")]
+ [DataRow("LastCommittedWriteRegion")]
public async Task ReadConsistencyStrategyRequestOptionSetsHeaders(string strategyName)
{
Cosmos.ReadConsistencyStrategy strategy = Enum.Parse(strategyName);
@@ -837,6 +838,18 @@ public async Task ReadConsistencyStrategyRequestOptionSetsHeaders(string strateg
Assert.AreEqual(strategy.ToString(), request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy]);
Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel],
"ConsistencyLevel header should not be set when ReadConsistencyStrategy is used");
+
+ if (strategy == Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion)
+ {
+ Assert.AreEqual(bool.TrueString, request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ "Hub region header should be set for LastCommittedWriteRegion on a read operation");
+ }
+ else
+ {
+ Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ $"Hub region header should NOT be set for {strategyName} strategy");
+ }
+
return TestHandler.ReturnSuccess();
});
@@ -862,20 +875,82 @@ public async Task ReadConsistencyStrategyRequestOptionSetsHeaders(string strateg
}
[TestMethod]
- public async Task ReadConsistencyStrategyClientLevelApplied()
+ [DataRow("Query", DisplayName = "Query sets hub header")]
+ [DataRow("SqlQuery", DisplayName = "SqlQuery sets hub header")]
+ [DataRow("ReadFeed", DisplayName = "ReadFeed (ChangeFeed) sets hub header")]
+ public async Task LastCommittedWriteRegionSetsHubHeaderForNonPointReadOperations(string operationTypeName)
+ {
+ OperationType operationType = Enum.Parse(operationTypeName);
+ using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong);
+
+ TestHandler testHandler = new TestHandler((request, cancellationToken) =>
+ {
+ Assert.AreEqual(
+ Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion.ToString(),
+ request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy],
+ "ReadConsistencyStrategy header should be set");
+ Assert.AreEqual(
+ bool.TrueString,
+ request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ $"Hub region header should be set for LastCommittedWriteRegion on {operationTypeName}");
+
+ return TestHandler.ReturnSuccess();
+ });
+
+ RequestInvokerHandler invoker = new RequestInvokerHandler(
+ client,
+ requestedClientConsistencyLevel: null,
+ requestedClientReadConsistencyStrategy: null,
+ requestedClientPriorityLevel: null,
+ requestedClientThroughputBucket: null)
+ {
+ InnerHandler = testHandler
+ };
+
+ RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs"))
+ {
+ ResourceType = ResourceType.Document
+ };
+ requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]");
+ requestMessage.OperationType = operationType;
+ requestMessage.RequestOptions = new ItemRequestOptions
+ {
+ ReadConsistencyStrategy = Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion
+ };
+
+ await invoker.SendAsync(requestMessage, new CancellationToken());
+ }
+
+ [TestMethod]
+ [DataRow("LatestCommitted")]
+ [DataRow("LastCommittedWriteRegion")]
+ public async Task ReadConsistencyStrategyClientLevelApplied(string strategyName)
{
// Verify client-level ReadConsistencyStrategy is applied when no request-level is set.
+ Cosmos.ReadConsistencyStrategy strategy = Enum.Parse(strategyName);
using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(
accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong,
- customizeClientBuilder: builder => builder.WithReadConsistencyStrategy(Cosmos.ReadConsistencyStrategy.LatestCommitted));
+ customizeClientBuilder: builder => builder.WithReadConsistencyStrategy(strategy));
TestHandler testHandler = new TestHandler((request, cancellationToken) =>
{
Assert.AreEqual(
- Cosmos.ReadConsistencyStrategy.LatestCommitted.ToString(),
+ strategy.ToString(),
request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy]);
Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel],
"ConsistencyLevel header should not be set when ReadConsistencyStrategy is used");
+
+ if (strategy == Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion)
+ {
+ Assert.AreEqual(bool.TrueString, request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ "Hub region header should be set when client-level LastCommittedWriteRegion is used on a read");
+ }
+ else
+ {
+ Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ $"Hub region header should NOT be set for {strategyName} strategy");
+ }
+
return TestHandler.ReturnSuccess();
});
@@ -984,5 +1059,166 @@ public async Task ReadConsistencyStrategyRequestLevelOverridesClientLevel()
await invoker.SendAsync(requestMessage, new CancellationToken());
}
+ [TestMethod]
+ public async Task LastCommittedWriteRegionOnWriteOperation_NoHubHeader()
+ {
+ // LastCommittedWriteRegion on a write operation should NOT set the hub region header.
+ using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong);
+
+ TestHandler testHandler = new TestHandler((request, cancellationToken) =>
+ {
+ Assert.AreEqual(
+ Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion.ToString(),
+ request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy],
+ "ReadConsistencyStrategy header should still be set on writes");
+ Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ "Hub region header should NOT be set for write operations even with LastCommittedWriteRegion");
+ return TestHandler.ReturnSuccess();
+ });
+
+ RequestInvokerHandler invoker = new RequestInvokerHandler(
+ client,
+ requestedClientConsistencyLevel: null,
+ requestedClientReadConsistencyStrategy: null,
+ requestedClientPriorityLevel: null,
+ requestedClientThroughputBucket: null)
+ {
+ InnerHandler = testHandler
+ };
+
+ RequestMessage requestMessage = new RequestMessage(HttpMethod.Post, new System.Uri("https://dummy.documents.azure.com:443/dbs"))
+ {
+ ResourceType = ResourceType.Document
+ };
+ requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]");
+ requestMessage.OperationType = OperationType.Create;
+ requestMessage.RequestOptions = new ItemRequestOptions { ReadConsistencyStrategy = Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion };
+
+ await invoker.SendAsync(requestMessage, new CancellationToken());
+ }
+
+ [TestMethod]
+ public async Task NoReadConsistencyStrategy_HubRegionHeaderNotSet()
+ {
+ // Verify hub region header is NOT set when ReadConsistencyStrategy is not specified.
+ using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(accountConsistencyLevel: Cosmos.ConsistencyLevel.Session);
+
+ TestHandler testHandler = new TestHandler((request, cancellationToken) =>
+ {
+ Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy],
+ "ReadConsistencyStrategy header should not be set");
+ Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ "Hub region header should not be set when ReadConsistencyStrategy is not used");
+ return TestHandler.ReturnSuccess();
+ });
+
+ RequestInvokerHandler invoker = new RequestInvokerHandler(
+ client,
+ requestedClientConsistencyLevel: Cosmos.ConsistencyLevel.Session,
+ requestedClientReadConsistencyStrategy: null,
+ requestedClientPriorityLevel: null,
+ requestedClientThroughputBucket: null)
+ {
+ InnerHandler = testHandler
+ };
+
+ RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs"))
+ {
+ ResourceType = ResourceType.Document
+ };
+ requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]");
+ requestMessage.OperationType = OperationType.Read;
+
+ await invoker.SendAsync(requestMessage, new CancellationToken());
+ }
+
+ [TestMethod]
+ public async Task LastCommittedWriteRegionWithConsistencyLevel_BothHeadersAndHubRegionSet()
+ {
+ // When both ConsistencyLevel and LastCommittedWriteRegion are set,
+ // all three headers should be present on a read operation.
+ using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong);
+
+ TestHandler testHandler = new TestHandler((request, cancellationToken) =>
+ {
+ Assert.AreEqual(
+ Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion.ToString(),
+ request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy],
+ "ReadConsistencyStrategy header should be set");
+ Assert.AreEqual(
+ Cosmos.ConsistencyLevel.Eventual.ToString(),
+ request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel],
+ "ConsistencyLevel header should also be set");
+ Assert.AreEqual(bool.TrueString, request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ "Hub region header should be set for LastCommittedWriteRegion on a read");
+ return TestHandler.ReturnSuccess();
+ });
+
+ RequestInvokerHandler invoker = new RequestInvokerHandler(
+ client,
+ requestedClientConsistencyLevel: null,
+ requestedClientReadConsistencyStrategy: null,
+ requestedClientPriorityLevel: null,
+ requestedClientThroughputBucket: null)
+ {
+ InnerHandler = testHandler
+ };
+
+ RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs"))
+ {
+ ResourceType = ResourceType.Document
+ };
+ requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]");
+ requestMessage.OperationType = OperationType.Read;
+ requestMessage.RequestOptions = new ItemRequestOptions
+ {
+ ConsistencyLevel = Cosmos.ConsistencyLevel.Eventual,
+ ReadConsistencyStrategy = Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion
+ };
+
+ await invoker.SendAsync(requestMessage, new CancellationToken());
+ }
+
+ [TestMethod]
+ public async Task LastCommittedWriteRegionRequestLevel_OverridesClientLevel_HubHeaderSet()
+ {
+ // Request-level LastCommittedWriteRegion should override client-level Eventual and set hub header.
+ using CosmosClient client = MockCosmosUtil.CreateMockCosmosClient(
+ accountConsistencyLevel: Cosmos.ConsistencyLevel.Strong,
+ customizeClientBuilder: builder => builder.WithReadConsistencyStrategy(Cosmos.ReadConsistencyStrategy.Eventual));
+
+ TestHandler testHandler = new TestHandler((request, cancellationToken) =>
+ {
+ Assert.AreEqual(
+ Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion.ToString(),
+ request.Headers[HttpConstants.HttpHeaders.ReadConsistencyStrategy]);
+ Assert.IsNull(request.Headers[HttpConstants.HttpHeaders.ConsistencyLevel],
+ "ConsistencyLevel header should not be set when no ConsistencyLevel was specified");
+ Assert.AreEqual(bool.TrueString, request.Headers[HttpConstants.HttpHeaders.ShouldProcessOnlyInHubRegion],
+ "Hub region header should be set when request-level LastCommittedWriteRegion overrides client-level");
+ return TestHandler.ReturnSuccess();
+ });
+
+ RequestInvokerHandler invoker = new RequestInvokerHandler(
+ client,
+ requestedClientConsistencyLevel: null,
+ requestedClientReadConsistencyStrategy: client.ClientOptions.ReadConsistencyStrategy,
+ requestedClientPriorityLevel: null,
+ requestedClientThroughputBucket: null)
+ {
+ InnerHandler = testHandler
+ };
+
+ RequestMessage requestMessage = new RequestMessage(HttpMethod.Get, new System.Uri("https://dummy.documents.azure.com:443/dbs"))
+ {
+ ResourceType = ResourceType.Document
+ };
+ requestMessage.Headers.Add(HttpConstants.HttpHeaders.PartitionKey, "[]");
+ requestMessage.OperationType = OperationType.Read;
+ requestMessage.RequestOptions = new ItemRequestOptions { ReadConsistencyStrategy = Cosmos.ReadConsistencyStrategy.LastCommittedWriteRegion };
+
+ await invoker.SendAsync(requestMessage, new CancellationToken());
+ }
+
}
-}
\ No newline at end of file
+}