Skip to content

Commit 4e81ed6

Browse files
[INTERNAL] AsyncCache: Adds Parameter to Enable Exception Handling Using CosmosClientOptions (#5073)
# Pull Request Template ## Description This PR enables `AsyncCache` and `AsyncCacheNonBlocking` Exception Handling Using `CosmosClientOptions`. This new client option will eventually used to get exception handling optimization (see PR #5069) shipped behind this new client option: `EnableAsyncCacheExceptionSharing`. By default this option is set to `true`. ## Type of change Please delete options that are not relevant. - [x] New feature (non-breaking change which adds functionality) ## Closing issues To automatically close an issue: closes #IssueNumber --------- Co-authored-by: Kiran Kumar Kolli <[email protected]>
1 parent 2745311 commit 4e81ed6

24 files changed

+167
-107
lines changed

Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,14 @@ public System.Text.Json.JsonSerializerOptions UseSystemTextJsonSerializerWithOpt
444444
/// <remarks>
445445
/// <para>This is optimal for latency-sensitive workloads. Does not apply if <see cref="ConnectionMode.Gateway"/> is used.</para>
446446
/// </remarks>
447-
internal bool? EnableAdvancedReplicaSelectionForTcp { get; set; }
447+
internal bool? EnableAdvancedReplicaSelectionForTcp { get; set; }
448+
449+
/// <summary>
450+
/// Gets or sets stack trace optimization to reduce stack trace proliferation in high-concurrency scenarios where exceptions are frequently thrown.
451+
/// When enabled, critical SDK components optimize exception handling to minimize performance overhead.
452+
/// The default value is 'true'.
453+
/// </summary>
454+
internal bool EnableAsyncCacheExceptionNoSharing { get; set; } = true;
448455

449456
/// <summary>
450457
/// (Direct/TCP) Controls the amount of idle time after which unused connections are closed.

Microsoft.Azure.Cosmos/src/DocumentClient.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ internal partial class DocumentClient : IDisposable, IAuthorizationTokenProvider
118118

119119
private readonly bool IsLocalQuorumConsistency = false;
120120
private readonly bool isReplicaAddressValidationEnabled;
121+
private readonly bool enableAsyncCacheExceptionNoSharing;
121122

122123
//Fault Injection
123124
private readonly IChaosInterceptorFactory chaosInterceptorFactory;
@@ -243,7 +244,9 @@ public DocumentClient(Uri serviceEndpoint,
243244
}
244245

245246
this.Initialize(serviceEndpoint, connectionPolicy, desiredConsistencyLevel);
246-
this.initTaskCache = new AsyncCacheNonBlocking<string, bool>(cancellationToken: this.cancellationTokenSource.Token);
247+
this.initTaskCache = new AsyncCacheNonBlocking<string, bool>(
248+
cancellationToken: this.cancellationTokenSource.Token,
249+
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
247250
this.isReplicaAddressValidationEnabled = ConfigurationManager.IsReplicaAddressValidationEnabled(connectionPolicy);
248251
}
249252

@@ -444,6 +447,7 @@ internal DocumentClient(Uri serviceEndpoint,
444447
/// <param name="remoteCertificateValidationCallback">This delegate responsible for validating the third party certificate. </param>
445448
/// <param name="cosmosClientTelemetryOptions">This is distributed tracing flag</param>
446449
/// <param name="chaosInterceptorFactory">This is the chaos interceptor used for fault injection</param>
450+
/// <param name="enableAsyncCacheExceptionNoSharing">A boolean flag indicating if stack trace optimization is enabled.</param>
447451
/// <remarks>
448452
/// The service endpoint can be obtained from the Azure Management Portal.
449453
/// If you are connecting using one of the Master Keys, these can be obtained along with the endpoint from the Azure Management Portal
@@ -472,7 +476,8 @@ internal DocumentClient(Uri serviceEndpoint,
472476
string cosmosClientId = null,
473477
RemoteCertificateValidationCallback remoteCertificateValidationCallback = null,
474478
CosmosClientTelemetryOptions cosmosClientTelemetryOptions = null,
475-
IChaosInterceptorFactory chaosInterceptorFactory = null)
479+
IChaosInterceptorFactory chaosInterceptorFactory = null,
480+
bool enableAsyncCacheExceptionNoSharing = true)
476481
{
477482
if (sendingRequestEventArgs != null)
478483
{
@@ -491,10 +496,13 @@ internal DocumentClient(Uri serviceEndpoint,
491496
this.receivedResponse += receivedResponseEventArgs;
492497
}
493498

499+
this.enableAsyncCacheExceptionNoSharing = enableAsyncCacheExceptionNoSharing;
494500
this.cosmosAuthorization = cosmosAuthorization ?? throw new ArgumentNullException(nameof(cosmosAuthorization));
495501
this.transportClientHandlerFactory = transportClientHandlerFactory;
496502
this.IsLocalQuorumConsistency = isLocalQuorumConsistency;
497-
this.initTaskCache = new AsyncCacheNonBlocking<string, bool>(cancellationToken: this.cancellationTokenSource.Token);
503+
this.initTaskCache = new AsyncCacheNonBlocking<string, bool>(
504+
cancellationToken: this.cancellationTokenSource.Token,
505+
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
498506
this.chaosInterceptorFactory = chaosInterceptorFactory;
499507
this.chaosInterceptor = chaosInterceptorFactory?.CreateInterceptor(this);
500508

@@ -675,8 +683,9 @@ private async Task OpenPrivateAsync(CancellationToken cancellationToken)
675683
storeModel: this.GatewayStoreModel,
676684
tokenProvider: this,
677685
retryPolicy: this.retryPolicy,
678-
telemetryToServiceHelper: this.telemetryToServiceHelper);
679-
this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache, this.GlobalEndpointManager);
686+
telemetryToServiceHelper: this.telemetryToServiceHelper,
687+
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
688+
this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache, this.GlobalEndpointManager, this.enableAsyncCacheExceptionNoSharing);
680689

681690
DefaultTrace.TraceWarning("{0} occurred while OpenAsync. Exception Message: {1}", ex.ToString(), ex.Message);
682691
}
@@ -938,7 +947,7 @@ internal virtual void Initialize(Uri serviceEndpoint,
938947
servicePoint.ConnectionLimit = this.ConnectionPolicy.MaxConnectionLimit;
939948
#endif
940949

941-
this.GlobalEndpointManager = new GlobalEndpointManager(this, this.ConnectionPolicy);
950+
this.GlobalEndpointManager = new GlobalEndpointManager(this, this.ConnectionPolicy, this.enableAsyncCacheExceptionNoSharing);
942951
this.PartitionKeyRangeLocation = this.ConnectionPolicy.EnablePartitionLevelFailover || this.ConnectionPolicy.EnablePartitionLevelCircuitBreaker
943952
? new GlobalPartitionEndpointManagerCore(
944953
this.GlobalEndpointManager,
@@ -1059,8 +1068,9 @@ private async Task<bool> GetInitializationTaskAsync(IStoreClientFactory storeCli
10591068
storeModel: this.GatewayStoreModel,
10601069
tokenProvider: this,
10611070
retryPolicy: this.retryPolicy,
1062-
telemetryToServiceHelper: this.telemetryToServiceHelper);
1063-
this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache, this.GlobalEndpointManager);
1071+
telemetryToServiceHelper: this.telemetryToServiceHelper,
1072+
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
1073+
this.partitionKeyRangeCache = new PartitionKeyRangeCache(this, this.GatewayStoreModel, this.collectionCache, this.GlobalEndpointManager, this.enableAsyncCacheExceptionNoSharing);
10641074
this.ResetSessionTokenRetryPolicy = new ResetSessionTokenRetryPolicyFactory(this.sessionContainer, this.collectionCache, this.retryPolicy);
10651075

10661076
gatewayStoreModel.SetCaches(this.partitionKeyRangeCache, this.collectionCache);
@@ -6722,7 +6732,8 @@ private void InitializeDirectConnectivity(IStoreClientFactory storeClientFactory
67226732
this.accountServiceConfiguration,
67236733
this.ConnectionPolicy,
67246734
this.httpClient,
6725-
this.storeClientFactory.GetConnectionStateListener());
6735+
this.storeClientFactory.GetConnectionStateListener(),
6736+
this.enableAsyncCacheExceptionNoSharing);
67266737

67276738
this.CreateStoreModel(subscribeRntbdStatus: true);
67286739
}

Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ internal static CosmosClientContext Create(
8585
cosmosClientId: cosmosClient.Id,
8686
remoteCertificateValidationCallback: ClientContextCore.SslCustomValidationCallBack(clientOptions.GetServerCertificateCustomValidationCallback()),
8787
cosmosClientTelemetryOptions: clientOptions.CosmosClientTelemetryOptions,
88-
chaosInterceptorFactory: clientOptions.ChaosInterceptorFactory);
88+
chaosInterceptorFactory: clientOptions.ChaosInterceptorFactory,
89+
enableAsyncCacheExceptionNoSharing: clientOptions.EnableAsyncCacheExceptionNoSharing);
8990

9091
return ClientContextCore.Create(
9192
cosmosClient,

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,26 @@ namespace Microsoft.Azure.Cosmos.Common
2020
/// <typeparam name="TValue">Type of values.</typeparam>
2121
internal sealed class AsyncCache<TKey, TValue>
2222
{
23+
private readonly bool enableAsyncCacheExceptionNoSharing;
2324
private readonly IEqualityComparer<TValue> valueEqualityComparer;
2425
private readonly IEqualityComparer<TKey> keyEqualityComparer;
2526

2627
private ConcurrentDictionary<TKey, AsyncLazy<TValue>> values;
2728

28-
public AsyncCache(IEqualityComparer<TValue> valueEqualityComparer, IEqualityComparer<TKey> keyEqualityComparer = null)
29+
public AsyncCache(
30+
IEqualityComparer<TValue> valueEqualityComparer,
31+
IEqualityComparer<TKey> keyEqualityComparer = null,
32+
bool enableAsyncCacheExceptionNoSharing = true)
2933
{
3034
this.keyEqualityComparer = keyEqualityComparer ?? EqualityComparer<TKey>.Default;
3135
this.values = new ConcurrentDictionary<TKey, AsyncLazy<TValue>>(this.keyEqualityComparer);
3236
this.valueEqualityComparer = valueEqualityComparer;
37+
this.enableAsyncCacheExceptionNoSharing = enableAsyncCacheExceptionNoSharing;
3338
}
3439

35-
public AsyncCache()
36-
: this(EqualityComparer<TValue>.Default)
40+
public AsyncCache(bool enableAsyncCacheExceptionNoSharing = true)
41+
: this(valueEqualityComparer: EqualityComparer<TValue>.Default,
42+
enableAsyncCacheExceptionNoSharing: enableAsyncCacheExceptionNoSharing)
3743
{
3844
}
3945

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace Microsoft.Azure.Cosmos
2020
/// </summary>
2121
internal sealed class AsyncCacheNonBlocking<TKey, TValue> : IDisposable
2222
{
23+
private readonly bool enableAsyncCacheExceptionNoSharing;
2324
private readonly CancellationTokenSource cancellationTokenSource;
2425
private readonly ConcurrentDictionary<TKey, AsyncLazyWithRefreshTask<TValue>> values;
2526
private readonly Func<Exception, bool> removeFromCacheOnBackgroundRefreshException;
@@ -30,18 +31,22 @@ internal sealed class AsyncCacheNonBlocking<TKey, TValue> : IDisposable
3031
public AsyncCacheNonBlocking(
3132
Func<Exception, bool> removeFromCacheOnBackgroundRefreshException = null,
3233
IEqualityComparer<TKey> keyEqualityComparer = null,
33-
CancellationToken cancellationToken = default)
34+
CancellationToken cancellationToken = default,
35+
bool enableAsyncCacheExceptionNoSharing = true)
3436
{
3537
this.keyEqualityComparer = keyEqualityComparer ?? EqualityComparer<TKey>.Default;
3638
this.values = new ConcurrentDictionary<TKey, AsyncLazyWithRefreshTask<TValue>>(this.keyEqualityComparer);
3739
this.removeFromCacheOnBackgroundRefreshException = removeFromCacheOnBackgroundRefreshException ?? AsyncCacheNonBlocking<TKey, TValue>.RemoveNotFoundFromCacheOnException;
3840
this.cancellationTokenSource = cancellationToken == default
3941
? new CancellationTokenSource()
4042
: CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
43+
this.enableAsyncCacheExceptionNoSharing = enableAsyncCacheExceptionNoSharing;
4144
}
4245

43-
public AsyncCacheNonBlocking()
44-
: this(removeFromCacheOnBackgroundRefreshException: null, keyEqualityComparer: null)
46+
public AsyncCacheNonBlocking(bool enableAsyncCacheExceptionNoSharing = true)
47+
: this(removeFromCacheOnBackgroundRefreshException: null,
48+
keyEqualityComparer: null,
49+
enableAsyncCacheExceptionNoSharing: enableAsyncCacheExceptionNoSharing)
4550
{
4651
}
4752

@@ -279,7 +284,7 @@ public AsyncLazyWithRefreshTask(
279284

280285
public bool IsValueCreated => this.value != null;
281286

282-
public Task<T> GetValueAsync(
287+
public Task<T> GetValueAsync(
283288
Func<T, Task<T>> createValueFunc)
284289
{
285290
// The task was already created so just return it.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ public ClientCollectionCache(
3333
IStoreModel storeModel,
3434
ICosmosAuthorizationTokenProvider tokenProvider,
3535
IRetryPolicyFactory retryPolicy,
36-
TelemetryToServiceHelper telemetryToServiceHelper)
36+
TelemetryToServiceHelper telemetryToServiceHelper,
37+
bool enableAsyncCacheExceptionNoSharing = true)
38+
: base(enableAsyncCacheExceptionNoSharing)
3739
{
3840
this.storeModel = storeModel ?? throw new ArgumentNullException("storeModel");
3941
this.tokenProvider = tokenProvider;

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,17 @@ internal abstract class CollectionCache
2828
/// </summary>
2929
protected class InternalCache
3030
{
31-
internal InternalCache()
31+
internal InternalCache(
32+
bool enableAsyncCacheExceptionNoSharing = true)
3233
{
33-
this.collectionInfoByName = new AsyncCache<string, ContainerProperties>(new CollectionRidComparer());
34-
this.collectionInfoById = new AsyncCache<string, ContainerProperties>(new CollectionRidComparer());
34+
this.collectionInfoByName = new AsyncCache<string, ContainerProperties>(
35+
new CollectionRidComparer(),
36+
enableAsyncCacheExceptionNoSharing: enableAsyncCacheExceptionNoSharing);
37+
38+
this.collectionInfoById = new AsyncCache<string, ContainerProperties>(
39+
new CollectionRidComparer(),
40+
enableAsyncCacheExceptionNoSharing: enableAsyncCacheExceptionNoSharing);
41+
3542
this.collectionInfoByNameLastRefreshTime = new ConcurrentDictionary<string, DateTime>();
3643
this.collectionInfoByIdLastRefreshTime = new ConcurrentDictionary<string, DateTime>();
3744
}
@@ -48,11 +55,12 @@ internal InternalCache()
4855
/// </summary>
4956
protected readonly InternalCache[] cacheByApiList;
5057

51-
protected CollectionCache()
58+
protected CollectionCache(
59+
bool enableAsyncCacheExceptionNoSharing = true)
5260
{
5361
this.cacheByApiList = new InternalCache[2];
54-
this.cacheByApiList[0] = new InternalCache(); // for API version < 2018-12-31
55-
this.cacheByApiList[1] = new InternalCache(); // for API version >= 2018-12-31
62+
this.cacheByApiList[0] = new InternalCache(enableAsyncCacheExceptionNoSharing); // for API version < 2018-12-31
63+
this.cacheByApiList[1] = new InternalCache(enableAsyncCacheExceptionNoSharing); // for API version >= 2018-12-31
5664
}
5765

5866
/// <summary>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,15 @@ public GatewayAddressCache(
7171
IConnectionStateListener connectionStateListener,
7272
long suboptimalPartitionForceRefreshIntervalInSeconds = 600,
7373
bool enableTcpConnectionEndpointRediscovery = false,
74-
bool replicaAddressValidationEnabled = false)
74+
bool replicaAddressValidationEnabled = false,
75+
bool enableAsyncCacheExceptionNoSharing = true)
7576
{
7677
this.addressEndpoint = new Uri(serviceEndpoint + "/" + Paths.AddressPathSegment);
7778
this.protocol = protocol;
7879
this.tokenProvider = tokenProvider;
7980
this.serviceEndpoint = serviceEndpoint;
8081
this.serviceConfigReader = serviceConfigReader;
81-
this.serverPartitionAddressCache = new AsyncCacheNonBlocking<PartitionKeyRangeIdentity, PartitionAddressInformation>();
82+
this.serverPartitionAddressCache = new AsyncCacheNonBlocking<PartitionKeyRangeIdentity, PartitionAddressInformation>(enableAsyncCacheExceptionNoSharing);
8283
this.suboptimalServerPartitionTimestamps = new ConcurrentDictionary<PartitionKeyRangeIdentity, DateTime>();
8384
this.serverPartitionAddressToPkRangeIdMap = new ConcurrentDictionary<ServerKey, HashSet<PartitionKeyRangeIdentity>>();
8485
this.suboptimalMasterPartitionTimestamp = DateTime.MaxValue;

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ internal sealed class GlobalAddressResolver : IAddressResolverExtension, IDispos
3939
private readonly ConcurrentDictionary<Uri, EndpointCache> addressCacheByEndpoint;
4040
private readonly bool enableTcpConnectionEndpointRediscovery;
4141
private readonly bool isReplicaAddressValidationEnabled;
42+
private readonly bool enableAsyncCacheExceptionNoSharing;
4243
private readonly IConnectionStateListener connectionStateListener;
4344
private IOpenConnectionsHandler openConnectionsHandler;
4445

@@ -52,7 +53,8 @@ public GlobalAddressResolver(
5253
IServiceConfigurationReader serviceConfigReader,
5354
ConnectionPolicy connectionPolicy,
5455
CosmosHttpClient httpClient,
55-
IConnectionStateListener connectionStateListener)
56+
IConnectionStateListener connectionStateListener,
57+
bool enableAsyncCacheExceptionNoSharing = true)
5658
{
5759
this.endpointManager = endpointManager;
5860
this.partitionKeyRangeLocationCache = partitionKeyRangeLocationCache;
@@ -72,6 +74,8 @@ public GlobalAddressResolver(
7274

7375
this.isReplicaAddressValidationEnabled = ConfigurationManager.IsReplicaAddressValidationEnabled(connectionPolicy);
7476

77+
this.enableAsyncCacheExceptionNoSharing = enableAsyncCacheExceptionNoSharing;
78+
7579
this.maxEndpoints = maxBackupReadEndpoints + 2; // for write and alternate write endpoint (during failover)
7680

7781
this.addressCacheByEndpoint = new ConcurrentDictionary<Uri, EndpointCache>();
@@ -344,7 +348,8 @@ private EndpointCache GetOrAddEndpoint(Uri endpoint)
344348
this.openConnectionsHandler,
345349
this.connectionStateListener,
346350
enableTcpConnectionEndpointRediscovery: this.enableTcpConnectionEndpointRediscovery,
347-
replicaAddressValidationEnabled: this.isReplicaAddressValidationEnabled);
351+
replicaAddressValidationEnabled: this.isReplicaAddressValidationEnabled,
352+
enableAsyncCacheExceptionNoSharing: this.enableAsyncCacheExceptionNoSharing);
348353

349354
string location = this.endpointManager.GetLocation(endpoint);
350355
AddressResolver addressResolver = new AddressResolver(null, new NullRequestSigner(), location);

0 commit comments

Comments
 (0)