Skip to content

Commit b7b3a37

Browse files
committed
Update retry header logic
1 parent 728e61e commit b7b3a37

3 files changed

Lines changed: 191 additions & 146 deletions

File tree

Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -327,22 +327,22 @@ private async Task<ShouldRetryResult> ShouldRetryInternalAsync(
327327
markBothReadAndWriteAsUnavailable: false,
328328
forceRefresh: false,
329329
retryOnPreferredLocations: true);
330-
}
331-
332-
if (statusCode == HttpStatusCode.NotFound
333-
&& subStatusCode == SubStatusCodes.ReadSessionNotAvailable)
330+
}
331+
332+
if (statusCode == HttpStatusCode.NotFound && subStatusCode == SubStatusCodes.ReadSessionNotAvailable)
334333
{
335334
#if !INTERNAL
336335
// Only set the hub region processing header for single master accounts
337-
if (!this.canUseMultipleWriteLocations)
336+
// Set header only after the first retry attempt fails with 404/1002
337+
if (!this.canUseMultipleWriteLocations && this.sessionTokenRetryCount >= 1)
338338
{
339339
this.addHubRegionProcessingOnlyHeader = true;
340340
}
341341
#endif
342342
return this.ShouldRetryOnSessionNotAvailable(this.documentServiceRequest);
343-
}
344-
345-
// Received 503 due to client connect timeout or Gateway
343+
}
344+
345+
// Received 503 due to client connect timeout or Gateway
346346
if (statusCode == HttpStatusCode.ServiceUnavailable)
347347
{
348348
return this.TryMarkEndpointUnavailableForPkRangeAndRetryOnServiceUnavailable(

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

Lines changed: 103 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -4319,113 +4319,110 @@ private static async Task GivenItemAsyncWhenMissingMemberHandlingIsErrorThenExpe
43194319
}
43204320
}
43214321

4322-
[TestMethod]
4323-
[Owner("aavasthy")]
4324-
[Description("Forces a single 404/1002 from the gateway and verifies ClientRetryPolicy adds x-ms-cosmos-hub-region-processing-only on the retry request.")]
4325-
public async Task ReadItemAsync_ShouldAddHubHeader_OnRetryAfter_404_1002()
4326-
{
4327-
bool headerObservedOnRetry = false;
4328-
int requestCount = 0;
4329-
bool shouldReturn404 = true;
4330-
4331-
// Created HTTP handler to intercept requests
4332-
HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
4333-
{
4334-
RequestCallBack = (request, cancellationToken) =>
4335-
{
4336-
// Track all document read requests
4337-
if (request.Method == HttpMethod.Get &&
4338-
request.RequestUri != null &&
4339-
request.RequestUri.AbsolutePath.Contains("/docs/"))
4340-
{
4341-
requestCount++;
4342-
4343-
// Check for hub header on retry (2nd+ request)
4344-
if (requestCount > 1 &&
4345-
request.Headers.TryGetValues(HubRegionHeader, out IEnumerable<string> values) &&
4346-
values.Any(v => v.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase)))
4347-
{
4348-
headerObservedOnRetry = true;
4349-
}
4350-
}
4351-
4352-
return Task.FromResult<HttpResponseMessage>(null);
4353-
},
4354-
4355-
ResponseIntercepter = (response, request) =>
4356-
{
4357-
if (shouldReturn404 &&
4358-
request.Method == HttpMethod.Get &&
4359-
request.RequestUri != null &&
4360-
request.RequestUri.AbsolutePath.Contains("/docs/"))
4361-
{
4362-
shouldReturn404 = false; // Only return 404 once
4363-
4364-
var errorResponse = new
4365-
{
4366-
code = "NotFound",
4367-
message = "Message: {\"Errors\":[\"Resource Not Found. Learn more: https://aka.ms/cosmosdb-tsg-not-found\"]}\r\nActivityId: " + Guid.NewGuid() + ", Request URI: " + request.RequestUri,
4368-
additionalErrorInfo = ""
4369-
};
4370-
4371-
HttpResponseMessage notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound)
4372-
{
4373-
Content = new StringContent(
4374-
JsonConvert.SerializeObject(errorResponse),
4375-
Encoding.UTF8,
4376-
"application/json"
4377-
)
4378-
};
4379-
4380-
// Add the substatus header for ReadSessionNotAvailable
4381-
notFoundResponse.Headers.Add("x-ms-substatus", "1002");
4382-
notFoundResponse.Headers.Add("x-ms-activity-id", Guid.NewGuid().ToString());
4383-
notFoundResponse.Headers.Add("x-ms-request-charge", "1.0");
4384-
4385-
return Task.FromResult(notFoundResponse);
4386-
}
4387-
4388-
return Task.FromResult(response);
4389-
}
4390-
};
4391-
4392-
CosmosClientOptions clientOptions = new CosmosClientOptions
4393-
{
4394-
ConnectionMode = ConnectionMode.Gateway,
4395-
ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
4396-
HttpClientFactory = () => new HttpClient(httpHandler),
4397-
MaxRetryAttemptsOnRateLimitedRequests = 9,
4398-
MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(30)
4399-
};
4400-
4401-
using CosmosClient customClient = TestCommon.CreateCosmosClient(clientOptions);
4402-
4403-
Container customContainer = customClient.GetContainer(this.database.Id, this.Container.Id);
4404-
4405-
// Create a test item first
4406-
ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
4407-
await this.Container.CreateItemAsync(testItem, new Cosmos.PartitionKey(testItem.pk));
4408-
4409-
try
4410-
{
4411-
// This should trigger 404/1002 on first attempt, then retry with hub header
4412-
ItemResponse<ToDoActivity> response = await customContainer.ReadItemAsync<ToDoActivity>(
4413-
testItem.id,
4414-
new Cosmos.PartitionKey(testItem.pk));
4415-
4416-
Assert.IsNotNull(response);
4417-
Assert.IsNotNull(response.Resource);
4418-
}
4419-
catch (CosmosException)
4420-
{
4421-
// It's possible the retry also fails, but should still have seen the retry attempt
4422-
}
4423-
4424-
// Verifying retry happened
4425-
Assert.IsTrue(requestCount >= 2, $"Expected at least 2 requests (original + retry), but got {requestCount}");
4426-
Assert.IsTrue(headerObservedOnRetry, $"Expected retry request to include '{HubRegionHeader}: true'");
4322+
[TestMethod]
4323+
[Owner("aavasthy")]
4324+
[Description("Forces two consecutive 404/1002 responses from the gateway and verifies ClientRetryPolicy sets the hub region header flag after the first retry fails.")]
4325+
public async Task ReadItemAsync_ShouldAddHubHeader_OnRetryAfter_404_1002()
4326+
{
4327+
int requestCount = 0;
4328+
int return404Count = 0;
4329+
const int maxReturn404 = 2; // Return 404/1002 twice
4330+
4331+
// Created HTTP handler to intercept requests
4332+
HttpClientHandlerHelper httpHandler = new HttpClientHandlerHelper
4333+
{
4334+
RequestCallBack = (request, cancellationToken) =>
4335+
{
4336+
// Track all document read requests
4337+
if (request.Method == HttpMethod.Get &&
4338+
request.RequestUri != null &&
4339+
request.RequestUri.AbsolutePath.Contains("/docs/"))
4340+
{
4341+
requestCount++;
4342+
4343+
// Header should NOT be present on first retry (2nd request)
4344+
if (requestCount == 2 &&
4345+
request.Headers.TryGetValues(HubRegionHeader, out IEnumerable<string> firstRetryValues) &&
4346+
firstRetryValues.Any())
4347+
{
4348+
Assert.Fail("Header should NOT be present on first retry attempt.");
4349+
}
4350+
4351+
// Return fake 404/1002 for first two requests
4352+
if (return404Count < maxReturn404)
4353+
{
4354+
return404Count++;
4355+
4356+
var errorResponse = new
4357+
{
4358+
code = "NotFound",
4359+
message = "Message: {\"Errors\":[\"Resource Not Found. Learn more: https://aka.ms/cosmosdb-tsg-not-found\"]}\r\nActivityId: " + Guid.NewGuid() + ", Request URI: " + request.RequestUri,
4360+
additionalErrorInfo = ""
4361+
};
4362+
4363+
HttpResponseMessage notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound)
4364+
{
4365+
Content = new StringContent(
4366+
JsonConvert.SerializeObject(errorResponse),
4367+
Encoding.UTF8,
4368+
"application/json"
4369+
)
4370+
};
4371+
4372+
// Add the substatus header for ReadSessionNotAvailable
4373+
notFoundResponse.Headers.Add("x-ms-substatus", "1002");
4374+
notFoundResponse.Headers.Add("x-ms-activity-id", Guid.NewGuid().ToString());
4375+
notFoundResponse.Headers.Add("x-ms-request-charge", "1.0");
4376+
4377+
return Task.FromResult(notFoundResponse);
4378+
}
4379+
}
4380+
4381+
return Task.FromResult<HttpResponseMessage>(null);
4382+
}
4383+
};
4384+
4385+
CosmosClientOptions clientOptions = new CosmosClientOptions
4386+
{
4387+
ConnectionMode = ConnectionMode.Gateway,
4388+
ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
4389+
HttpClientFactory = () => new HttpClient(httpHandler),
4390+
MaxRetryAttemptsOnRateLimitedRequests = 9,
4391+
MaxRetryWaitTimeOnRateLimitedRequests = TimeSpan.FromSeconds(30)
4392+
};
4393+
4394+
using CosmosClient customClient = TestCommon.CreateCosmosClient(clientOptions);
4395+
4396+
Container customContainer = customClient.GetContainer(this.database.Id, this.Container.Id);
4397+
4398+
// Create a test item first
4399+
ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
4400+
await this.Container.CreateItemAsync(testItem, new Cosmos.PartitionKey(testItem.pk));
4401+
4402+
try
4403+
{
4404+
// This should trigger 404/1002 twice
4405+
// In single-region emulator, after first retry fails with 404/1002, it won't retry again
4406+
ItemResponse<ToDoActivity> response = await customContainer.ReadItemAsync<ToDoActivity>(
4407+
testItem.id,
4408+
new Cosmos.PartitionKey(testItem.pk));
4409+
4410+
Assert.Fail("Expected CosmosException due to consecutive 404/1002 failures.");
4411+
}
4412+
catch (CosmosException ex)
4413+
{
4414+
// Expected: After first retry fails with 404/1002, single master won't retry again
4415+
Assert.AreEqual(HttpStatusCode.NotFound, ex.StatusCode);
4416+
Assert.AreEqual((int)SubStatusCodes.ReadSessionNotAvailable, ex.SubStatusCode);
4417+
}
4418+
4419+
// Verify the expected behavior:
4420+
// 1. Initial request (requestCount = 1) fails with 404/1002
4421+
// 2. First retry (requestCount = 2) fails with 404/1002
4422+
// 3. No more retries because single master + no additional regions
4423+
Assert.AreEqual(2, requestCount, $"Expected exactly 2 requests (initial + 1 retry) for single-region emulator, but got {requestCount}");
4424+
Assert.AreEqual(2, return404Count, "Both requests should have returned 404/1002");
44274425
}
4428-
44294426

44304427
private async Task<T> AutoGenerateIdPatternTest<T>(Cosmos.PartitionKey pk, T itemWithoutId)
44314428
{

0 commit comments

Comments
 (0)