Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
487b485
Enabling exception less
kirankumarkolli May 16, 2025
8ded201
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli May 16, 2025
875c1e8
Fixign the test
kirankumarkolli May 16, 2025
15b516c
Merge branch 'users/kirankk/enable_exception_less' of https://github.…
kirankumarkolli May 16, 2025
999b63a
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli May 21, 2025
b72bfe4
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Jun 3, 2025
60fa1ce
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Jun 17, 2025
197e200
Adding more troublehsooting context
kirankumarkolli Jun 18, 2025
06c4d0f
Upgrading to direct 3.40.1
kirankumarkolli Jul 10, 2025
a437b13
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Jul 10, 2025
1ba61fc
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Jul 15, 2025
002658a
Clean up merge conflicts: revert Directory.Build.props and CosmosItem…
Copilot Jan 14, 2026
49fd90a
[WIP] Add enabling exception less for 404 and 403 (#5559)
Copilot Jan 15, 2026
4a41df9
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Jan 15, 2026
405bef6
Fixing the test
kirankumarkolli Jan 15, 2026
fc4f6dd
Add internal repository reference to ExceptionLessTests documentation…
Copilot Jan 15, 2026
2d0af85
Enable exception-less execution for 404/1002, 403, and 400 status cod…
Copilot Jan 15, 2026
547677d
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Mar 26, 2026
d695880
Merge branch 'master' into users/kirankk/enable_exception_less
kirankumarkolli Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Microsoft.Azure.Cosmos/src/Handler/RequestMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ internal DocumentServiceRequest ToDocumentServiceRequest()

serviceRequest.UseStatusCodeForFailures = true;
serviceRequest.UseStatusCodeFor429 = true;
serviceRequest.UseStatusCodeFor4041002 = true;
serviceRequest.UseStatusCodeFor403 = true;
serviceRequest.UseStatusCodeForBadRequest = true;
serviceRequest.Properties = this.Properties;
this.DocumentServiceRequest = serviceRequest;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3400,8 +3400,11 @@ public async Task VerifySessionNotFoundStatistics()
Assert.Fail("Should had thrown ReadSessionNotAvailable");
}
catch (CosmosException cosmosException)
{
Assert.IsTrue(cosmosException.Message.Contains("The read session is not available for the input session token."), cosmosException.Message);
{
Assert.AreEqual(StatusCodes.NotFound, cosmosException.StatusCode);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.AreEqual failed. Expected:<NotFound (Microsoft.Azure.Documents.StatusCodes)>. Actual:<NotFound (System.Net.HttpStatusCode)>.
Stack Trace:
at Microsoft.Azure.Cosmos.SDK.EmulatorTests.CosmosItemTests.VerifySessionNotFoundStatistics() in D:\a_work\1\s\Microsoft.Azure.Cosmos\tests\Microsoft.Azure.Cosmos.EmulatorTests\CosmosItemTests.cs:line 3402
at Microsoft.Azure.Cosmos.SDK.EmulatorTests.CosmosItemTests.VerifySessionNotFoundStatistics() in D:\a_work\1\s\Microsoft.Azure.Cosmos\tests\Microsoft.Azure.Cosmos.EmulatorTests\CosmosItemTests.cs:line 3412

Change Expected to be actual value

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot look into it

Assert.AreEqual(SubStatusCodes.ReadSessionNotAvailable, cosmosException.SubStatusCode);

Assert.IsTrue(cosmosException.Message.Contains("The read/write session is not available"), cosmosException.Message);
string exception = cosmosException.ToString();
Assert.IsTrue(exception.Contains("Point Operation Statistics"), exception);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
namespace Microsoft.Azure.Cosmos
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot look into it

{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Cosmos.Diagnostics;
using Microsoft.Azure.Cosmos.SDK.EmulatorTests;
using Microsoft.Azure.Cosmos.Tracing.TraceData;
using Microsoft.Azure.Documents;
using Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// Tests for exception-less behavior in Cosmos DB operations.
/// </summary>
/// <remarks>
/// Reference: https://msdata.visualstudio.com/CosmosDB/_git/CosmosDB?path=/Product/SDK/.net/Microsoft.Azure.Cosmos.Friends/tests/ExceptionLessTests.cs
/// </remarks>
[VisualStudio.TestTools.UnitTesting.TestClass]
public class ExceptionLessTests
{
private readonly ConcurrentBag<Exception> Exceptions = new();

#nullable enable
private void ExceptionCaptureHandler(object? sender, FirstChanceExceptionEventArgs eventArgs)
#nullable disable
{
this.Exceptions.Add(eventArgs.Exception);
}

[TestInitialize]
public void TestInit()
{
// Subscribe to the FirstChanceException event
AppDomain.CurrentDomain.FirstChanceException += this.ExceptionCaptureHandler;

TraceSource traceSource = (TraceSource)typeof(DefaultTrace).GetProperty("TraceSource").GetValue(null);
traceSource.Switch.Level = SourceLevels.All;
traceSource.Listeners.Clear();
traceSource.Listeners.Add(new ConsoleTraceListener());
}

[TestCleanup]
public void TestCleanup()
{
// Subscribe to the FirstChanceException event
AppDomain.CurrentDomain.FirstChanceException -= this.ExceptionCaptureHandler;
this.Exceptions.Clear();
}

/// <summary>
/// Test for exception less behavior with session not found scenarios
/// </summary>
[TestMethod]
[Owner("kirankk")]
[DataRow(ConnectionMode.Gateway, Cosmos.ConsistencyLevel.Session)]
[DataRow(ConnectionMode.Direct, Cosmos.ConsistencyLevel.Session)]
public async Task SessionNotFoundTestAsync(ConnectionMode mode,
Cosmos.ConsistencyLevel consistencyLevel)
{
string databaseId = Guid.NewGuid().ToString();
string containerId = Guid.NewGuid().ToString();

CosmosClientOptions clientOptions = new CosmosClientOptions()
{
ConnectionMode = mode,
RequestTimeout = TimeSpan.FromHours(10),
EnableUpgradeConsistencyToLocalQuorum = true,
};

using (CosmosClient cosmosClient = TestCommon.CreateCosmosClient(clientOptions))
{
await cosmosClient.CreateDatabaseIfNotExistsAsync(databaseId);
await cosmosClient.GetDatabase(databaseId).CreateContainerIfNotExistsAsync(containerId, "/id");

await cosmosClient.InitializeContainersAsync(new List<(string, string)>() { (databaseId, containerId) }, CancellationToken.None);

Container container = cosmosClient.GetContainer(databaseId, containerId);
ContainerProperties containerProperties = await container.ReadContainerAsync();

TestObj testObj = new TestObj() { id = Guid.NewGuid().ToString() };

ItemResponse<TestObj> createRespMsg = await container.CreateItemAsync<TestObj>(testObj, new Cosmos.PartitionKey(testObj.id));

DocumentServiceRequest.DefaultUseStatusCodeFor4041002 = false;

Trace.TraceInformation($"{Environment.NewLine}First Read (may be cold start) (UseStatusCodeFor4041002={DocumentServiceRequest.DefaultUseStatusCodeFor4041002})");
ResponseMessage respMsg = await container.ReadItemStreamAsync(testObj.id, new Cosmos.PartitionKey(testObj.id),
new ItemRequestOptions() { ConsistencyLevel = consistencyLevel });
this.TraceResponseMessageAndAssert(respMsg);

string futureLsn = this.GetFutureLsn(respMsg.Headers.Session);
Trace.TraceInformation($"{Environment.NewLine}Second ReadFor404-1002 (UseStatusCodeFor4041002={DocumentServiceRequest.DefaultUseStatusCodeFor4041002}): {futureLsn}");
respMsg = await container.ReadItemStreamAsync(testObj.id, new Cosmos.PartitionKey(testObj.id),
new ItemRequestOptions() { SessionToken = futureLsn, ConsistencyLevel = consistencyLevel });
SummaryDiagnostics summaryDiagnostics1 = new SummaryDiagnostics(((CosmosTraceDiagnostics)respMsg.Diagnostics).Value);
this.TraceResponseMessageAndAssert(respMsg);

Trace.TraceInformation($"{Environment.NewLine}Third Read (rebase if-any) (UseStatusCodeFor4041002={DocumentServiceRequest.DefaultUseStatusCodeFor4041002})");
respMsg = await container.ReadItemStreamAsync(testObj.id, new Cosmos.PartitionKey(testObj.id),
new ItemRequestOptions() { ConsistencyLevel = consistencyLevel });
this.TraceResponseMessageAndAssert(respMsg);

DocumentServiceRequest.DefaultUseStatusCodeFor4041002 = true;

Trace.TraceInformation($"{Environment.NewLine}ReadFor404-1002 (UseStatusCodeFor4041002={DocumentServiceRequest.DefaultUseStatusCodeFor4041002}): {futureLsn}");
respMsg = await container.ReadItemStreamAsync(testObj.id, new Cosmos.PartitionKey(testObj.id),
new ItemRequestOptions() { SessionToken = futureLsn, ConsistencyLevel = consistencyLevel });
SummaryDiagnostics summaryDiagnostics2 = new SummaryDiagnostics(((CosmosTraceDiagnostics)respMsg.Diagnostics).Value);
this.TraceResponseMessageAndAssert(respMsg, expectedExceptionCount: 0);

Assert.IsTrue(summaryDiagnostics1.AllRegionsContacted.Value.SetEquals(summaryDiagnostics2.AllRegionsContacted.Value), $"AllRegionsContacted");
CollectionAssert.AreEquivalent(summaryDiagnostics1.GatewayRequestsSummary.Value, summaryDiagnostics2.GatewayRequestsSummary.Value, "GatewayRequestsSummary");

// Direct #retries are expected to be different (exception vs exceptionless flows)
if (mode == ConnectionMode.Direct)
{
CollectionAssert.AreEquivalent(summaryDiagnostics1.DirectRequestsSummary.Value.Keys, summaryDiagnostics2.DirectRequestsSummary.Value.Keys);
Assert.AreEqual(1, summaryDiagnostics1.DirectRequestsSummary.Value.Keys.Count);

(int statusCode, int subStatusCode) = summaryDiagnostics1.DirectRequestsSummary.Value.Keys.First();
int exceptionFlowRetryCount = summaryDiagnostics1.DirectRequestsSummary.Value[(statusCode, subStatusCode)];
int exceptionLessFlowRetryCount = summaryDiagnostics2.DirectRequestsSummary.Value[(statusCode, subStatusCode)];
Assert.IsTrue(exceptionFlowRetryCount == exceptionLessFlowRetryCount
|| (exceptionLessFlowRetryCount > exceptionFlowRetryCount && ((exceptionLessFlowRetryCount - exceptionFlowRetryCount) / exceptionFlowRetryCount * 100) < 10),
$"DirectRequestsSummary: {string.Join(Environment.NewLine, summaryDiagnostics1.DirectRequestsSummary.Value.Select(e => $"{e.Key} -> {e.Value}"))} {Environment.NewLine} {string.Join(Environment.NewLine, summaryDiagnostics2.DirectRequestsSummary.Value.Select(e => $"{e.Key} -> {e.Value}"))}");
}

// Delete the database
await cosmosClient.GetDatabase(databaseId).DeleteAsync();
}
}

private string GetFutureLsn(string sessionTokenStr)
{
if (SessionTokenHelper.TryParse(sessionTokenStr, out string partitionKeyRangeId, out ISessionToken parsedSessionToken))
{
VectorSessionToken vectorSessionToken = (VectorSessionToken)parsedSessionToken;
if (vectorSessionToken != null)
{
ISessionToken futureSessionToken = new VectorSessionToken(vectorSessionToken, vectorSessionToken.LSN + 50);
return $"{partitionKeyRangeId}:{futureSessionToken.ConvertToString()}";
}
}

throw new ArgumentException($"Failed for {sessionTokenStr}");
}

private void TraceResponseMessageAndAssert(ResponseMessage respMsg,
int? expectedExceptionCount = null)
{
IEnumerable<string> nonHttpExceptions = this.Exceptions.Select(e => e.StackTrace).Where(e => !e.Contains("System.Net.Http.HttpConnection"));
int currentExceptionCount = nonHttpExceptions.Count();
Trace.TraceInformation($"(StatusCode, SubStatusCode): {respMsg.StatusCode} -> {respMsg.Headers.SubStatusCode}");
Trace.TraceInformation($"SessionToken(Request -> Response): {respMsg.RequestMessage.Headers.Session} -> {respMsg.Headers.Session}");
Trace.TraceInformation($"Exception count: {currentExceptionCount}");
Trace.TraceInformation($"Distinct Msg's: {string.Join(Environment.NewLine, this.Exceptions.Select(e => e.Message).GroupBy(e => e, (gpkey, gpValues) => $"{gpkey} -> {gpValues.Count()}"))}");
Trace.TraceInformation(respMsg.Diagnostics.ToString());

if (expectedExceptionCount.HasValue)
{
Assert.AreEqual(expectedExceptionCount, currentExceptionCount,
$"{string.Join(Environment.NewLine, nonHttpExceptions.Distinct())}");
}

this.Exceptions.Clear();
}

public class TestObj
{
#pragma warning disable SA1300 // Element should begin with upper-case letter
public string id { get; set; }
#pragma warning restore SA1300 // Element should begin with upper-case letter
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public async Task ReadOrQueryOperationWithMaxInRegionRetryCountZero(FaultInjecti

// Assert that only the original attempt happened (no retries)
long hitCount = badSessionTokenRule.GetHitCount();
Assert.AreEqual(4, hitCount, $"There should be only one attempt (no retries) for {faultInjectionOperationType} when MaxInRegionRetryCount is 0 and RemotePreferredRegion is set to true.");
Assert.AreEqual(4, hitCount, $"There should be only one attempt (no retries) for {faultInjectionOperationType} when MaxInRegionRetryCount is 0 and RemotePreferredRegion is set to true. Diagnostics {executionResult.Diagnostics}");
}

}
Expand Down Expand Up @@ -174,7 +174,7 @@ public async Task ReadOperationWithReadSessionUnavailableTest(FaultInjectionOper

if (remoteRegionPreferred)
{
Assert.IsTrue(hitCount >= sessionTokenMismatchRetryAttempts && hitCount <= (1 + sessionTokenMismatchRetryAttempts) * 4);
Assert.IsTrue(hitCount >= sessionTokenMismatchRetryAttempts && hitCount <= (1 + sessionTokenMismatchRetryAttempts) * 4, executionResult.Diagnostics.ToString());
}
}
}
Expand Down Expand Up @@ -261,7 +261,7 @@ public async Task WriteOperationWithReadSessionUnavailableTest(FaultInjectionOpe
if (remoteRegionPreferred)
{
// higher hit count is possible while in MinRetryWaitTimeWithinRegion
Assert.IsTrue(hitCount >= sessionTokenMismatchRetryAttempts);
Assert.IsTrue(hitCount >= sessionTokenMismatchRetryAttempts, executionResult.Diagnostics.ToString());
}
}
}
Expand Down
Loading