Skip to content

Commit 6e9fdee

Browse files
committed
Async Cache and Async Cache Nonblocking exception handling changes
- Added support to post-process the exception in above mentioned two cache classes that creates a shallow copy of specific exception types to prevent stack trace proliferation and rethrows them, while propagating all other exceptions unchanged. - Added support for TaskCanceledException, TimeoutException and OperationCanceledException in this drop
1 parent 5e1dd0c commit 6e9fdee

File tree

5 files changed

+109
-16
lines changed

5 files changed

+109
-16
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// ------------------------------------------------------------
4+
5+
namespace Microsoft.Azure.Cosmos
6+
{
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Text;
10+
using System.Threading.Tasks;
11+
using Microsoft.Azure.Documents;
12+
13+
/// <summary>
14+
/// Utility for post-processing of exceptions.
15+
/// </summary>
16+
internal static class ExceptionHandlingUtility
17+
{
18+
public const string ExceptionHandlingForStackTraceOptimizationEnabled = "AZURE_COSMOS_STACK_TRACE_OPTIMIZATION_ENABLED";
19+
/// <summary>
20+
/// Creates a shallow copy of specific exception types (e.g., TaskCanceledException, TimeoutException, OperationCanceledException)
21+
/// to prevent excessive stack trace growth and rethrows them. All other exceptions are not processed.
22+
/// </summary>
23+
public static void CloneAndRethrowException(Exception e)
24+
{
25+
if (e is TaskCanceledException)
26+
{
27+
TaskCanceledException taskCanceledEx = new TaskCanceledException(e.Message, e.InnerException);
28+
taskCanceledEx.Data["Message"] = e.Data["Message"];
29+
throw taskCanceledEx;
30+
}
31+
32+
if (e is TimeoutException)
33+
{
34+
TimeoutException timeoutEx = new TimeoutException(e.Message, e.InnerException);
35+
timeoutEx.Data["Message"] = e.Data["Message"];
36+
throw timeoutEx;
37+
}
38+
39+
if (e is DocumentClientException dce)
40+
{
41+
//TODO. Add support for shallow object clones method in the direct package.
42+
DocumentClientException clonedDocumentClientEx = new DocumentClientException(
43+
dce.Message,
44+
dce.InnerException,
45+
dce.StatusCode);
46+
clonedDocumentClientEx.Data["Message"] = e.Data["Message"];
47+
throw clonedDocumentClientEx;
48+
}
49+
50+
if (e is ICloneable ex)
51+
{
52+
throw (Exception)ex.Clone();
53+
}
54+
}
55+
}
56+
}

Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.Azure.Cosmos
1717
/// <summary>
1818
/// The Cosmos Client exception
1919
/// </summary>
20-
public class CosmosException : Exception
20+
public class CosmosException : Exception, ICloneable
2121
{
2222
private readonly string stackTrace;
2323
private readonly Lazy<string> lazyMessage;
@@ -196,7 +196,7 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request)
196196
trace: this.Trace);
197197

198198
return responseMessage;
199-
}
199+
}
200200

201201
private static string GetMessageHelper(
202202
HttpStatusCode statusCode,
@@ -301,6 +301,17 @@ internal static void RecordOtelAttributes(CosmosException exception, DiagnosticS
301301
{
302302
CosmosDbEventSource.RecordDiagnosticsForExceptions(exception.Diagnostics);
303303
}
304-
}
304+
}
305+
306+
/// <summary>
307+
/// Creates a shallow copy of the current exception instance.
308+
/// This ensures that the cloned exception retains the same properties but does not
309+
/// excessively proliferate stack traces or deep-copy unnecessary objects.
310+
/// </summary>
311+
/// <returns>A shallow copy of the current <see cref="CosmosException"/>.</returns>
312+
public object Clone()
313+
{
314+
return this.MemberwiseClone();
315+
}
305316
}
306317
}

Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosOperationCanceledException.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace Microsoft.Azure.Cosmos
1818
/// diagnostics of the operation that was canceled.
1919
/// </summary>
2020
[Serializable]
21-
public class CosmosOperationCanceledException : OperationCanceledException
21+
public class CosmosOperationCanceledException : OperationCanceledException, ICloneable
2222
{
2323
private readonly OperationCanceledException originalException;
2424
private readonly Lazy<string> lazyMessage;
@@ -155,6 +155,17 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont
155155
info.AddValue("tokenCancellationRequested", this.tokenCancellationRequested);
156156
info.AddValue("lazyMessage", this.lazyMessage.Value);
157157
info.AddValue("toStringMessage", this.toStringMessage.Value);
158-
}
158+
}
159+
160+
/// <summary>
161+
/// Creates a shallow copy of the current exception instance.
162+
/// This ensures that the cloned exception retains the same properties but does not
163+
/// excessively proliferate stack traces or deep-copy unnecessary objects.
164+
/// </summary>
165+
/// <returns>A shallow copy of the current <see cref="CosmosOperationCanceledException"/>.</returns>
166+
public object Clone()
167+
{
168+
return this.MemberwiseClone();
169+
}
159170
}
160171
}

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ internal sealed class AsyncCache<TKey, TValue>
2323
private readonly IEqualityComparer<TValue> valueEqualityComparer;
2424
private readonly IEqualityComparer<TKey> keyEqualityComparer;
2525

26-
private ConcurrentDictionary<TKey, AsyncLazy<TValue>> values;
27-
26+
private ConcurrentDictionary<TKey, AsyncLazy<TValue>> values;
27+
private static readonly bool isStackTraceOptimizationEnabled = false;
2828
public AsyncCache(IEqualityComparer<TValue> valueEqualityComparer, IEqualityComparer<TKey> keyEqualityComparer = null)
2929
{
3030
this.keyEqualityComparer = keyEqualityComparer ?? EqualityComparer<TKey>.Default;
@@ -148,11 +148,18 @@ public async Task<TValue> GetAsync(
148148
{
149149
return await generator;
150150
}
151-
catch (Exception) when (object.ReferenceEquals(actualValue, newLazyValue))
151+
catch (Exception ex) when (object.ReferenceEquals(actualValue, newLazyValue))
152152
{
153153
// If the lambda this thread added to values triggered an exception remove it from the cache.
154-
this.TryRemoveValue(key, actualValue);
155-
throw;
154+
this.TryRemoveValue(key, actualValue);
155+
156+
if (isStackTraceOptimizationEnabled)
157+
{
158+
// Creates a shallow copy of specific exception types to prevent stack trace proliferation
159+
// and rethrows them, doesn't process other exceptions.
160+
ExceptionHandlingUtility.CloneAndRethrowException(ex);
161+
}
162+
throw ex;
156163
}
157164
}
158165

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ internal sealed class AsyncCacheNonBlocking<TKey, TValue> : IDisposable
2525
private readonly Func<Exception, bool> removeFromCacheOnBackgroundRefreshException;
2626

2727
private readonly IEqualityComparer<TKey> keyEqualityComparer;
28-
private bool isDisposed;
28+
private bool isDisposed;
29+
30+
private static readonly bool isStackTraceOptimizationEnabled = false;
2931

3032
public AsyncCacheNonBlocking(
3133
Func<Exception, bool> removeFromCacheOnBackgroundRefreshException = null,
@@ -114,9 +116,15 @@ public async Task<TValue> GetAsync(
114116
key,
115117
removed,
116118
e);
117-
}
118-
119-
throw;
119+
}
120+
121+
if (isStackTraceOptimizationEnabled)
122+
{
123+
// Creates a shallow copy of specific exception types to prevent stack trace proliferation
124+
// and rethrows them, doesn't process other exceptions.
125+
ExceptionHandlingUtility.CloneAndRethrowException(e);
126+
}
127+
throw e;
120128
}
121129

122130
return await this.UpdateCacheAndGetValueFromBackgroundTaskAsync(
@@ -158,7 +166,7 @@ public async Task<TValue> GetAsync(
158166
this.values.TryRemove(key, out _);
159167
throw;
160168
}
161-
}
169+
}
162170

163171
public void Set(
164172
TKey key,
@@ -279,7 +287,7 @@ public AsyncLazyWithRefreshTask(
279287

280288
public bool IsValueCreated => this.value != null;
281289

282-
public Task<T> GetValueAsync(
290+
public Task<T> GetValueAsync(
283291
Func<T, Task<T>> createValueFunc)
284292
{
285293
// The task was already created so just return it.

0 commit comments

Comments
 (0)