diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs
index b41e0aa2f8..b9c9c13fa0 100644
--- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs
+++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs
@@ -100,8 +100,11 @@ public class CosmosClient : IDisposable
private readonly string DatabaseRootUri = Paths.Databases_Root;
private ConsistencyLevel? accountConsistencyLevel;
private bool isDisposed = false;
+ private object disposedLock = new object();
internal static int numberOfClientsCreated;
+ internal static int NumberOfActiveClients;
+
internal DateTime? DisposedDateTimeUtc { get; private set; } = null;
static CosmosClient()
@@ -262,6 +265,7 @@ internal CosmosClient(
clientOptions ??= new CosmosClientOptions();
this.ClientId = this.IncrementNumberOfClientsCreated();
+
this.ClientContext = ClientContextCore.Create(
this,
clientOptions);
@@ -1232,9 +1236,27 @@ private Task InitializeContainersAsync(IReadOnlyList<(string databaseId, string
private int IncrementNumberOfClientsCreated()
{
+ this.IncrementNumberOfActiveClients();
+
return Interlocked.Increment(ref numberOfClientsCreated);
}
+ private int IncrementNumberOfActiveClients()
+ {
+ return Interlocked.Increment(ref NumberOfActiveClients);
+ }
+
+ private int DecrementNumberOfActiveClients()
+ {
+ // In case dispose is called multiple times. Check if at least 1 active client is there
+ if (NumberOfActiveClients > 0)
+ {
+ return Interlocked.Decrement(ref NumberOfActiveClients);
+ }
+
+ return 0;
+ }
+
private async Task InitializeContainerAsync(string databaseId, string containerId, CancellationToken cancellationToken = default)
{
ContainerInternal container = (ContainerInternal)this.GetContainer(databaseId, containerId);
@@ -1280,17 +1302,22 @@ public void Dispose()
/// True if disposing
protected virtual void Dispose(bool disposing)
{
- if (!this.isDisposed)
+ lock (this.disposedLock)
{
- this.DisposedDateTimeUtc = DateTime.UtcNow;
-
- if (disposing)
+ if (this.isDisposed == true)
{
- this.ClientContext.Dispose();
+ return;
}
-
this.isDisposed = true;
}
+
+ this.DisposedDateTimeUtc = DateTime.UtcNow;
+
+ if (disposing)
+ {
+ this.ClientContext.Dispose();
+ this.DecrementNumberOfActiveClients();
+ }
}
}
}
diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientConfigurationTraceDatum.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientConfigurationTraceDatum.cs
index 353afccec3..7fa91b5fd5 100644
--- a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientConfigurationTraceDatum.cs
+++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ClientConfigurationTraceDatum.cs
@@ -28,6 +28,7 @@ public ClientConfigurationTraceDatum(CosmosClientContext cosmosClientContext, Da
cosmosClientContext.ClientOptions.ApplicationPreferredRegions);
this.cachedNumberOfClientCreated = CosmosClient.numberOfClientsCreated;
+ this.cachedNumberOfActiveClient = CosmosClient.NumberOfActiveClients;
this.cachedUserAgentString = this.UserAgentContainer.UserAgent;
this.cachedSerializedJson = this.GetSerializedDatum();
}
@@ -46,9 +47,11 @@ public ReadOnlyMemory SerializedJson
{
get
{
- if ((this.cachedUserAgentString != this.UserAgentContainer.UserAgent) ||
- (this.cachedNumberOfClientCreated != CosmosClient.numberOfClientsCreated))
+ if (this.cachedUserAgentString != this.UserAgentContainer.UserAgent ||
+ this.cachedNumberOfClientCreated != CosmosClient.numberOfClientsCreated ||
+ this.cachedNumberOfActiveClient != CosmosClient.NumberOfActiveClients)
{
+ this.cachedNumberOfActiveClient = CosmosClient.NumberOfActiveClients;
this.cachedNumberOfClientCreated = CosmosClient.numberOfClientsCreated;
this.cachedUserAgentString = this.UserAgentContainer.UserAgent;
this.cachedSerializedJson = this.GetSerializedDatum();
@@ -62,6 +65,8 @@ public ReadOnlyMemory SerializedJson
private ReadOnlyMemory cachedSerializedJson;
private int cachedNumberOfClientCreated;
+ private int cachedNumberOfActiveClient;
+
private string cachedUserAgentString;
internal override void Accept(ITraceDatumVisitor traceDatumVisitor)
@@ -79,6 +84,8 @@ private ReadOnlyMemory GetSerializedDatum()
jsonTextWriter.WriteFieldName("NumberOfClientsCreated");
jsonTextWriter.WriteNumber64Value(this.cachedNumberOfClientCreated);
+ jsonTextWriter.WriteFieldName("NumberOfActiveClients");
+ jsonTextWriter.WriteNumber64Value(this.cachedNumberOfActiveClient);
jsonTextWriter.WriteFieldName("User Agent");
jsonTextWriter.WriteStringValue(this.cachedUserAgentString);
diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceTextWriter.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceTextWriter.cs
index caf0587d06..8fc6b214c7 100644
--- a/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceTextWriter.cs
+++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceWriter.TraceTextWriter.cs
@@ -479,6 +479,7 @@ public void Visit(ClientConfigurationTraceDatum clientConfigurationTraceDatum)
stringBuilder.AppendLine("Client Configuration");
stringBuilder.AppendLine($"Client Created Time: {clientConfigurationTraceDatum.ClientCreatedDateTimeUtc.ToString("o", CultureInfo.InvariantCulture)}");
stringBuilder.AppendLine($"Number Of Clients Created: {CosmosClient.numberOfClientsCreated}");
+ stringBuilder.AppendLine($"Number Of Active Clients: {CosmosClient.NumberOfActiveClients}");
stringBuilder.AppendLine($"User Agent: {clientConfigurationTraceDatum.UserAgentContainer.UserAgent}");
stringBuilder.AppendLine("Connection Config:");
stringBuilder.AppendLine($"{space}'gw': {clientConfigurationTraceDatum.GatewayConnectionConfig}");
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ActiveClientDiagnosticTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ActiveClientDiagnosticTest.cs
new file mode 100644
index 0000000000..348107434e
--- /dev/null
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ActiveClientDiagnosticTest.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//------------------------------------------------------------
+
+namespace Microsoft.Azure.Cosmos.Tests
+{
+ using System;
+ using System.Threading.Tasks;
+ using System.Xml.Serialization;
+ using VisualStudio.TestTools.UnitTesting;
+
+ [TestClass]
+ public class ActiveClientDiagnosticTest
+ {
+ private const string ConnectionString = "AccountEndpoint=https://localtestcosmos.documents.azure.com:443/;AccountKey=425Mcv8CXQqzRNCgFNjIhT424GK99CKJvASowTnq15Vt8LeahXTcN5wt3342vQ==;";
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ CosmosClient.NumberOfActiveClients = 0;
+ }
+
+ [TestMethod]
+ public void SingleClientTest()
+ {
+ CosmosClient cosmosClient = new CosmosClient(ConnectionString);
+ Assert.AreEqual(1, CosmosClient.NumberOfActiveClients);
+ cosmosClient.Dispose();
+ }
+
+ [TestMethod]
+ public void MultiClientTest()
+ {
+ CosmosClient cosmosClient1 = new CosmosClient(ConnectionString); // Initializing 1st time
+ CosmosClient cosmosClient2 = new CosmosClient(ConnectionString); // Initializing 2nd time
+ Assert.AreEqual(2, CosmosClient.NumberOfActiveClients);
+ cosmosClient1.Dispose();
+ cosmosClient2.Dispose();
+ }
+
+ [TestMethod]
+ public void MultiClientWithDisposeTest()
+ {
+ CosmosClient cosmosClient1 = new CosmosClient(ConnectionString); // Initializing 1st time
+ CosmosClient cosmosClient2 = new CosmosClient(ConnectionString); // Initializing 2nd time
+ CosmosClient cosmosClient3 = new CosmosClient(ConnectionString); // Initializing 3rd time
+ cosmosClient2.Dispose(); // Destroying 1 instance
+ Assert.AreEqual(2, CosmosClient.NumberOfActiveClients);
+ cosmosClient1.Dispose();
+ cosmosClient3.Dispose();
+ }
+
+ [TestMethod]
+ public void MultiThreadClientDisposeTest()
+ {
+ CosmosClient cosmosClient1 = new CosmosClient(ConnectionString); // Initializing 1st time
+ CosmosClient cosmosClient2 = new CosmosClient(ConnectionString);
+
+ Parallel.Invoke(cosmosClient2.Dispose,
+ cosmosClient2.Dispose,
+ cosmosClient2.Dispose,
+ cosmosClient2.Dispose,
+ cosmosClient2.Dispose);
+
+ // Initializing 2nd time
+ Assert.AreEqual(1, CosmosClient.NumberOfActiveClients);
+ cosmosClient1.Dispose();
+ cosmosClient2.Dispose();
+ }
+ }
+}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DefaultTracingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DefaultTracingTests.cs
index c17b9344de..011bdfe415 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DefaultTracingTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DefaultTracingTests.cs
@@ -19,8 +19,9 @@ public class DefaultTracingTests
[TestMethod]
public void DefaultTracingEnableTest()
{
- // Access cosmos client to cause the static consturctor to get called
+ // Access cosmos client to cause the static constructor to get called
Assert.IsTrue(CosmosClient.numberOfClientsCreated >= 0);
+ Assert.IsTrue(CosmosClient.NumberOfActiveClients >= 0);
if (!Debugger.IsAttached)
{