Skip to content

Commit 6fdac6b

Browse files
committed
Add unit tests for ExceptionHandlingUtility
1 parent a66c65c commit 6fdac6b

8 files changed

Lines changed: 13008 additions & 12712 deletions

File tree

Microsoft.Azure.Cosmos/src/ExceptionHandlingUtility.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,28 @@ internal static class ExceptionHandlingUtility
2121
/// </summary>
2222
public static void CloneAndRethrowException(Exception e)
2323
{
24-
if (e is ICloneable ex)
24+
Exception ex = e switch
2525
{
26-
throw (Exception)ex.Clone();
27-
}
26+
ICloneable cloneableEx => (Exception)cloneableEx.Clone(),
27+
TaskCanceledException taskCanceledEx => AddMessageData(new TaskCanceledException(taskCanceledEx.Message, taskCanceledEx), e),
28+
TimeoutException timeoutEx => AddMessageData(new TimeoutException(timeoutEx.Message, timeoutEx), e),
29+
_ => null
30+
};
2831

29-
if (e is TaskCanceledException)
32+
if (ex is not null)
3033
{
31-
TaskCanceledException taskCanceledEx = new TaskCanceledException(e.Message, e.InnerException);
32-
taskCanceledEx.Data["Message"] = e.Data["Message"];
33-
throw taskCanceledEx;
34+
throw ex;
3435
}
36+
}
3537

36-
if (e is TimeoutException)
38+
private static Exception AddMessageData(Exception target, Exception source)
39+
{
40+
if (source.Data.Contains("Message"))
3741
{
38-
TimeoutException timeoutEx = new TimeoutException(e.Message, e.InnerException);
39-
timeoutEx.Data["Message"] = e.Data["Message"];
40-
throw timeoutEx;
42+
target.Data["Message"] = source.Data["Message"];
4143
}
44+
45+
return target;
4246
}
4347
}
4448
}

Microsoft.Azure.Cosmos/src/Routing/AsyncCache.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,19 @@ public async Task<TValue> GetAsync(
164164
// and rethrows them, doesn't process other exceptions.
165165
ExceptionHandlingUtility.CloneAndRethrowException(ex);
166166
}
167-
throw ex;
168-
}
167+
throw;
168+
}
169+
catch (Exception ex)
170+
{
171+
if (this.enableAsyncCacheExceptionNoSharing)
172+
{
173+
// Creates a shallow copy of specific exception types to prevent stack trace proliferation
174+
// and rethrows them, doesn't process other exceptions.
175+
ExceptionHandlingUtility.CloneAndRethrowException(ex);
176+
}
177+
throw;
178+
}
179+
169180
}
170181

171182
public void Remove(TKey key)

Microsoft.Azure.Cosmos/src/Routing/AsyncCacheNonBlocking.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public async Task<TValue> GetAsync(
127127
// and rethrows them, doesn't process other exceptions.
128128
ExceptionHandlingUtility.CloneAndRethrowException(e);
129129
}
130-
throw e;
130+
throw;
131131
}
132132

133133
return await this.UpdateCacheAndGetValueFromBackgroundTaskAsync(
@@ -166,7 +166,14 @@ public async Task<TValue> GetAsync(
166166
e.ToString());
167167

168168
// Remove the failed task from the dictionary so future requests can send other calls..
169-
this.values.TryRemove(key, out _);
169+
this.values.TryRemove(key, out _);
170+
171+
if (this.enableAsyncCacheExceptionNoSharing)
172+
{
173+
// Creates a shallow copy of specific exception types to prevent stack trace proliferation
174+
// and rethrows them, doesn't process other exceptions.
175+
ExceptionHandlingUtility.CloneAndRethrowException(e);
176+
}
170177
throw;
171178
}
172179
}

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,86 @@ public async Task MultiRegionAccountTest()
938938
AccountProperties properties = await cosmosClient.ReadAccountAsync();
939939
Assert.IsNotNull(properties);
940940
}
941+
942+
[DataTestMethod]
943+
[DataRow(true)]
944+
[DataRow(false)]
945+
[Owner("amudumba")]
946+
public async Task ValidateAsyncExceptionNoSharing(bool asyncCacheExceptionNoSharing)
947+
{
948+
TimeoutException exception = new TimeoutException("HTTP Timeout exception", new TimeoutException("Inner exception message"));
949+
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
950+
{
951+
ConsistencyLevel = Cosmos.ConsistencyLevel.Session,
952+
//Set the pre-request event handler to throw a pre-defined timeout exception.
953+
SendingRequestEventArgs = (sender, e) =>
954+
{
955+
if (e.IsHttpRequest())
956+
{
957+
string endWith = "partitionKeyRangeIds=0";
958+
if (e.HttpRequest.RequestUri.OriginalString.EndsWith(endWith))
959+
{
960+
if (e.HttpRequest.Method == HttpMethod.Get &&
961+
e.HttpRequest.RequestUri.OriginalString.EndsWith(endWith))
962+
{
963+
throw exception;
964+
}
965+
}
966+
}
967+
},
968+
EnableAsyncCacheExceptionNoSharing = asyncCacheExceptionNoSharing
969+
};
970+
971+
Cosmos.Database db = null;
972+
try
973+
{
974+
CosmosClient cosmosClient = TestCommon.CreateCosmosClient(clientOptions: cosmosClientOptions);
975+
976+
db = await cosmosClient.CreateDatabaseIfNotExistsAsync("TimeoutFaultTest");
977+
Container container = await db.CreateContainerIfNotExistsAsync("TimeoutFaultContainer", "/pk");
978+
979+
// Shared TaskCompletionSource ensures all tasks start together
980+
TaskCompletionSource<object> tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
981+
int iterations = 3;// Simulating 3 concurrent write requests
982+
List<Task> createTasks = new List<Task>();
983+
984+
for (int i = 0; i < iterations; i++)
985+
{
986+
createTasks.Add(Task.Run(async () =>
987+
{
988+
await tcs.Task; // All tasks wait before proceeding
989+
990+
ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity();
991+
await container.CreateItemAsync(testItem);
992+
}));
993+
}
994+
995+
// Signal all tasks to start
996+
tcs.SetResult(null);
997+
998+
// Wait for all tasks to complete (they should all fail)
999+
await Task.WhenAll(createTasks);
1000+
1001+
1002+
}
1003+
catch (TimeoutException tex)
1004+
{
1005+
if (asyncCacheExceptionNoSharing)
1006+
{
1007+
//asyncCacheExceptionNoSharing feature is enabled. Shallow copies of the exception will be thrown.
1008+
Assert.IsFalse(Object.ReferenceEquals(tex, exception), "Exception should not be the same");
1009+
}
1010+
else
1011+
{
1012+
//asyncCacheExceptionNoSharing feature is disabled. Exceptions will be thrown as is.
1013+
Assert.IsTrue(Object.ReferenceEquals(tex, exception), "Exception should be the same");
1014+
}
1015+
}
1016+
finally
1017+
{
1018+
if (db != null) await db.DeleteAsync();
1019+
}
1020+
}
9411021

9421022
[TestMethod]
9431023
[Owner("amudumba")]

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ namespace Microsoft.Azure.Cosmos.Tests
1212
using System.Threading.Tasks;
1313
using Microsoft.Azure.Cosmos.Common;
1414
using Microsoft.Azure.Documents;
15-
using Microsoft.VisualStudio.TestTools.UnitTesting;
16-
15+
using Microsoft.VisualStudio.TestTools.UnitTesting;
1716
[TestClass]
1817
public class AsyncCacheTest
1918
{
@@ -306,6 +305,45 @@ await Task.Factory.StartNew(() =>
306305
TaskCreationOptions.None,
307306
new SingleTaskScheduler()
308307
);
308+
}
309+
310+
[DataTestMethod]
311+
[DataRow(true)]
312+
[DataRow(false)]
313+
public async Task GetAsync_Assert_OnExceptionPostProcessing(bool enabled)
314+
{
315+
AsyncCache<string, string> cacheDefault = new AsyncCache<string, string>(enableAsyncCacheExceptionNoSharing: enabled);
316+
317+
// Arrange
318+
Exception testException = new TimeoutException("Simulated timeout exception");
319+
CancellationToken cancellationToken = CancellationToken.None;
320+
321+
// Simulate a failing lambda function
322+
Func<Task<string>> failingLambda = () => Task.FromException<string>(testException);
323+
324+
// Act
325+
try
326+
{
327+
await cacheDefault.GetAsync(
328+
"testKey",
329+
obsoleteValue: null,
330+
singleValueInitFunc: failingLambda,
331+
cancellationToken,
332+
forceRefresh: false);
333+
}
334+
catch (TimeoutException ex)
335+
{
336+
if (enabled)
337+
{
338+
// Assert that the expected exception was rethrown
339+
Assert.IsFalse(Object.ReferenceEquals(testException, ex));
340+
} else
341+
{
342+
// Assert that no cloning and rethrowing was done on the exceptions,
343+
Assert.IsTrue(Object.ReferenceEquals(testException, ex));
344+
}
345+
346+
}
309347
}
310348

311349
private int GenerateIntFuncThatThrows()

0 commit comments

Comments
 (0)