From 6e216362783cf0fe64cab5b61536530ee4f8e6d6 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 6 Jun 2025 23:43:27 +0100 Subject: [PATCH 01/10] Expose access token cache count --- .../AuthenticationResult.cs | 1 + .../AuthenticationResultMetadata.cs | 6 ++ .../Cache/CacheSessionManager.cs | 8 ++- .../Cache/ITokenCacheAccessor.cs | 2 + ...nMemoryPartitionedAppTokenCacheAccessor.cs | 60 ++++++++++++++++--- ...MemoryPartitionedUserTokenCacheAccessor.cs | 57 +++++++++++++++--- .../PublicApi/net462/PublicAPI.Unshipped.txt | 2 + .../PublicApi/net472/PublicAPI.Unshipped.txt | 2 + .../net8.0-android/PublicAPI.Unshipped.txt | 2 + .../net8.0-ios/PublicAPI.Unshipped.txt | 2 + .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 2 + .../netstandard2.0/PublicAPI.Unshipped.txt | 2 + .../TelemetryCore/Internal/Events/ApiEvent.cs | 2 + .../AcquireTokenForClientCacheTests.cs | 5 +- .../CacheTests/InternalCacheOptionsTests.cs | 35 ++++++++--- .../CacheTests/LoadingProjectsTests.cs | 2 - .../ParallelRequestsTests.cs | 4 ++ 17 files changed, 164 insertions(+), 30 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index c75c921375..489b13936a 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -167,6 +167,7 @@ internal AuthenticationResult( CorrelationId = correlationID; ApiEvent = apiEvent; AuthenticationResultMetadata = new AuthenticationResultMetadata(tokenSource); + AuthenticationResultMetadata.CachedAccessTokenCount = apiEvent.CacheAccessTokenCount; AdditionalResponseParameters = msalAccessTokenCacheItem?.PersistedCacheParameters?.Count > 0 ? (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters : additionalResponseParameters; diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs b/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs index 58fce3acbf..e19ac5ddd7 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs @@ -97,5 +97,11 @@ public AuthenticationResultMetadata(TokenSource tokenSource) /// See https://aka.ms/msal-net-logging for more details about logging. /// public string Telemetry { get; set; } + + /// + /// The number of entries in the token cache. For app tokens, this is the number of access tokens cached. + /// For users tokens, an entry contains all tokens for a user, including refresh token and id tokens. + /// + public int CachedAccessTokenCount { get; set; } } } diff --git a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs index d4b74143ae..e47e131662 100644 --- a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs +++ b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs @@ -48,9 +48,11 @@ public async Task FindAccessTokenAsync() return await TokenCacheInternal.FindAccessTokenAsync(_requestParams).ConfigureAwait(false); } - public Task> SaveTokenResponseAsync(MsalTokenResponse tokenResponse) + public async Task> SaveTokenResponseAsync(MsalTokenResponse tokenResponse) { - return TokenCacheInternal.SaveTokenResponseAsync(_requestParams, tokenResponse); + var result = await TokenCacheInternal.SaveTokenResponseAsync(_requestParams, tokenResponse).ConfigureAwait(false); + RequestContext.ApiEvent.CacheAccessTokenCount = TokenCacheInternal.Accessor.EntryCount; + return result; } public async Task GetAccountAssociatedWithAccessTokenAsync(MsalAccessTokenCacheItem msalAccessTokenCacheItem) @@ -181,6 +183,8 @@ private async Task RefreshCacheForReadOperationsAsync() { RequestContext.ApiEvent.CacheLevel = CacheLevel.L1Cache; } + + RequestContext.ApiEvent.CacheAccessTokenCount = TokenCacheInternal.Accessor.EntryCount; } } } diff --git a/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs index 5efa34cb7c..1d73a84dcf 100644 --- a/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/Cache/ITokenCacheAccessor.cs @@ -10,6 +10,8 @@ namespace Microsoft.Identity.Client.Cache { internal interface ITokenCacheAccessor { + int EntryCount { get; } + void SaveAccessToken(MsalAccessTokenCacheItem item); void SaveRefreshToken(MsalRefreshTokenCacheItem item); diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs index 8ae392d260..886dc2cb31 100644 --- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using Microsoft.Identity.Client.Cache; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Core; @@ -34,6 +35,11 @@ internal class InMemoryPartitionedAppTokenCacheAccessor : ITokenCacheAccessor protected readonly ILoggerAdapter _logger; private readonly CacheOptions _tokenCacheAccessorOptions; + private int _entryCount = 0; + private static int s_entryCount = 0; + + public int EntryCount => _tokenCacheAccessorOptions.UseSharedCache ? s_entryCount : _entryCount; + public InMemoryPartitionedAppTokenCacheAccessor( ILoggerAdapter logger, CacheOptions tokenCacheAccessorOptions) @@ -59,9 +65,25 @@ public void SaveAccessToken(MsalAccessTokenCacheItem item) string itemKey = item.CacheKey; string partitionKey = CacheKeyFactory.GetAppTokenCacheItemKey(item.ClientId, item.TenantId, item.KeyId, item.AdditionalCacheKeyComponents); - // if a conflict occurs, pick the latest value - AccessTokenCacheDictionary - .GetOrAdd(partitionKey, new ConcurrentDictionary())[itemKey] = item; + var partition = AccessTokenCacheDictionary.GetOrAdd(partitionKey, _ => new ConcurrentDictionary()); + bool added = partition.TryAdd(itemKey, item); + if (added) + { + if (_tokenCacheAccessorOptions.UseSharedCache) + { + Interlocked.Increment(ref s_entryCount); + } + else + { + Interlocked.Increment(ref _entryCount); + } + } + else + { + // If not added, it means it was replaced. Only increment if it was a new key. + // If you want to treat replacement as not changing the count, do nothing here. + partition[itemKey] = item; + } } /// @@ -129,12 +151,26 @@ public void DeleteAccessToken(MsalAccessTokenCacheItem item) { var partitionKey = CacheKeyFactory.GetAppTokenCacheItemKey(item.ClientId, item.TenantId, item.KeyId); - AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition); - if (partition == null || !partition.TryRemove(item.CacheKey, out _)) + if (AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition)) { - _logger.InfoPii( - () => $"[Internal cache] Cannot delete access token because it was not found in the cache. Key {item.CacheKey}.", - () => "[Internal cache] Cannot delete access token because it was not found in the cache."); + bool removed = partition.TryRemove(item.CacheKey, out _); + if (removed) + { + if (_tokenCacheAccessorOptions.UseSharedCache) + { + Interlocked.Decrement(ref s_entryCount); + } + else + { + Interlocked.Decrement(ref _entryCount); + } + } + else + { + _logger.InfoPii( + () => $"[Internal cache] Cannot delete access token because it was not found in the cache. Key {item.CacheKey}.", + () => "[Internal cache] Cannot delete access token because it was not found in the cache."); + } } } @@ -219,6 +255,14 @@ public virtual void Clear(ILoggerAdapter requestlogger = null) { var logger = requestlogger ?? _logger; AccessTokenCacheDictionary.Clear(); + if (_tokenCacheAccessorOptions.UseSharedCache) + { + Interlocked.Exchange(ref s_entryCount, 0); + } + else + { + Interlocked.Exchange(ref _entryCount, 0); + } logger.Always("[Internal cache] Clearing app token cache accessor."); // app metadata isn't removable } diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs index 4e3800129b..4c895314b5 100644 --- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs @@ -41,9 +41,15 @@ internal class InMemoryPartitionedUserTokenCacheAccessor : ITokenCacheAccessor private static readonly ConcurrentDictionary s_appMetadataDictionary = new ConcurrentDictionary(); + private static int s_entryCount = 0; + protected readonly ILoggerAdapter _logger; private readonly CacheOptions _tokenCacheAccessorOptions; + private int _entryCount = 0; + + public int EntryCount => _tokenCacheAccessorOptions.UseSharedCache ? s_entryCount : _entryCount; + public InMemoryPartitionedUserTokenCacheAccessor(ILoggerAdapter logger, CacheOptions tokenCacheAccessorOptions) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -73,8 +79,23 @@ public void SaveAccessToken(MsalAccessTokenCacheItem item) string itemKey = item.CacheKey; string partitionKey = CacheKeyFactory.GetKeyFromCachedItem(item); - AccessTokenCacheDictionary - .GetOrAdd(partitionKey, new ConcurrentDictionary())[itemKey] = item; // if a conflict occurs, pick the latest value + var partition = AccessTokenCacheDictionary.GetOrAdd(partitionKey, _ => new ConcurrentDictionary()); + bool added = partition.TryAdd(itemKey, item); + if (added) + { + if (_tokenCacheAccessorOptions.UseSharedCache) + { + System.Threading.Interlocked.Increment(ref s_entryCount); + } + else + { + System.Threading.Interlocked.Increment(ref _entryCount); + } + } + else + { + partition[itemKey] = item; + } } public void SaveRefreshToken(MsalRefreshTokenCacheItem item) @@ -151,12 +172,26 @@ public void DeleteAccessToken(MsalAccessTokenCacheItem item) { string partitionKey = CacheKeyFactory.GetKeyFromCachedItem(item); - AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition); - if (partition == null || !partition.TryRemove(item.CacheKey, out _)) + if (AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition)) { - _logger.InfoPii( - () => $"[Internal cache] Cannot delete access token because it was not found in the cache. Key {item.CacheKey}.", - () => "[Internal cache] Cannot delete access token because it was not found in the cache."); + bool removed = partition.TryRemove(item.CacheKey, out _); + if (removed) + { + if (_tokenCacheAccessorOptions.UseSharedCache) + { + System.Threading.Interlocked.Decrement(ref s_entryCount); + } + else + { + System.Threading.Interlocked.Decrement(ref _entryCount); + } + } + else + { + _logger.InfoPii( + () => $"[Internal cache] Cannot delete access token because it was not found in the cache. Key {item.CacheKey}.", + () => "[Internal cache] Cannot delete access token because it was not found in the cache."); + } } } @@ -326,6 +361,14 @@ public virtual void Clear(ILoggerAdapter requestlogger = null) RefreshTokenCacheDictionary.Clear(); IdTokenCacheDictionary.Clear(); AccountCacheDictionary.Clear(); + if (_tokenCacheAccessorOptions.UseSharedCache) + { + System.Threading.Interlocked.Exchange(ref s_entryCount, 0); + } + else + { + System.Threading.Interlocked.Exchange(ref _entryCount, 0); + } // app metadata isn't removable } diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index 1164619fd4..9b7aaf512c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ Microsoft.Identity.Client.Utils.MacMainThreadScheduler.IsRunning() -> bool Microsoft.Identity.Client.Utils.MacMainThreadScheduler.Stop() -> void Microsoft.Identity.Client.Utils.MacMainThreadScheduler.RunOnMainThreadAsync(System.Func asyncAction) -> System.Threading.Tasks.Task Microsoft.Identity.Client.Utils.MacMainThreadScheduler.StartMessageLoop() -> void +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.get -> int +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.set -> void \ No newline at end of file diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index 1164619fd4..9b7aaf512c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ Microsoft.Identity.Client.Utils.MacMainThreadScheduler.IsRunning() -> bool Microsoft.Identity.Client.Utils.MacMainThreadScheduler.Stop() -> void Microsoft.Identity.Client.Utils.MacMainThreadScheduler.RunOnMainThreadAsync(System.Func asyncAction) -> System.Threading.Tasks.Task Microsoft.Identity.Client.Utils.MacMainThreadScheduler.StartMessageLoop() -> void +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.get -> int +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.set -> void \ No newline at end of file diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index 1164619fd4..9b7aaf512c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ Microsoft.Identity.Client.Utils.MacMainThreadScheduler.IsRunning() -> bool Microsoft.Identity.Client.Utils.MacMainThreadScheduler.Stop() -> void Microsoft.Identity.Client.Utils.MacMainThreadScheduler.RunOnMainThreadAsync(System.Func asyncAction) -> System.Threading.Tasks.Task Microsoft.Identity.Client.Utils.MacMainThreadScheduler.StartMessageLoop() -> void +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.get -> int +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.set -> void \ No newline at end of file diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index 1164619fd4..9b7aaf512c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ Microsoft.Identity.Client.Utils.MacMainThreadScheduler.IsRunning() -> bool Microsoft.Identity.Client.Utils.MacMainThreadScheduler.Stop() -> void Microsoft.Identity.Client.Utils.MacMainThreadScheduler.RunOnMainThreadAsync(System.Func asyncAction) -> System.Threading.Tasks.Task Microsoft.Identity.Client.Utils.MacMainThreadScheduler.StartMessageLoop() -> void +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.get -> int +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.set -> void \ No newline at end of file diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index 1164619fd4..9b7aaf512c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ Microsoft.Identity.Client.Utils.MacMainThreadScheduler.IsRunning() -> bool Microsoft.Identity.Client.Utils.MacMainThreadScheduler.Stop() -> void Microsoft.Identity.Client.Utils.MacMainThreadScheduler.RunOnMainThreadAsync(System.Func asyncAction) -> System.Threading.Tasks.Task Microsoft.Identity.Client.Utils.MacMainThreadScheduler.StartMessageLoop() -> void +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.get -> int +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.set -> void \ No newline at end of file diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index 1164619fd4..9b7aaf512c 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -5,3 +5,5 @@ Microsoft.Identity.Client.Utils.MacMainThreadScheduler.IsRunning() -> bool Microsoft.Identity.Client.Utils.MacMainThreadScheduler.Stop() -> void Microsoft.Identity.Client.Utils.MacMainThreadScheduler.RunOnMainThreadAsync(System.Func asyncAction) -> System.Threading.Tasks.Task Microsoft.Identity.Client.Utils.MacMainThreadScheduler.StartMessageLoop() -> void +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.get -> int +Microsoft.Identity.Client.AuthenticationResultMetadata.CachedAccessTokenCount.set -> void \ No newline at end of file diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs index 301a196c7b..053bfded3b 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs @@ -62,6 +62,8 @@ public string ApiIdString public string ApiErrorCode { get; set; } + public int CacheAccessTokenCount { get; set; } + #region Region public string RegionUsed { get; set; } diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs index 360466710c..d3b07222a1 100644 --- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs @@ -44,8 +44,8 @@ public class AcquireTokenForClientCacheTests (10000, 10), }; - [ParamsAllValues] - public bool EnableCacheSerialization { get; set; } + //[ParamsAllValues] + public bool EnableCacheSerialization { get; set; } = true; //[Params(false)] public bool UseMicrosoftIdentityWebCache { get; set; } @@ -57,6 +57,7 @@ public async Task GlobalSetupAsync() .Create(TestConstants.ClientId) .WithRedirectUri(TestConstants.RedirectUri) .WithClientSecret(TestConstants.ClientSecret) + .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) .WithLegacyCacheCompatibility(false) .BuildConcrete(); diff --git a/tests/Microsoft.Identity.Test.Unit/CacheTests/InternalCacheOptionsTests.cs b/tests/Microsoft.Identity.Test.Unit/CacheTests/InternalCacheOptionsTests.cs index de87882209..333255bedd 100644 --- a/tests/Microsoft.Identity.Test.Unit/CacheTests/InternalCacheOptionsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/CacheTests/InternalCacheOptionsTests.cs @@ -96,13 +96,13 @@ public async Task ClientCreds_StaticCache_Async() .BuildConcrete(); httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); - await ClientCredsAcquireAndAssertTokenSourceAsync(app1, "S1", TokenSource.IdentityProvider).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app1, "S1", TokenSource.IdentityProvider, 1).ConfigureAwait(false); httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); - await ClientCredsAcquireAndAssertTokenSourceAsync(app1, "S2", TokenSource.IdentityProvider).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app1, "S2", TokenSource.IdentityProvider, 2).ConfigureAwait(false); - await ClientCredsAcquireAndAssertTokenSourceAsync(app2, "S1", TokenSource.Cache).ConfigureAwait(false); - await ClientCredsAcquireAndAssertTokenSourceAsync(app2, "S2", TokenSource.Cache).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app2, "S1", TokenSource.Cache, 2).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app2, "S2", TokenSource.Cache, 2).ConfigureAwait(false); ConfidentialClientApplication app3 = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) @@ -111,14 +111,16 @@ public async Task ClientCreds_StaticCache_Async() .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) .BuildConcrete(); - await ClientCredsAcquireAndAssertTokenSourceAsync(app3, "S1", TokenSource.Cache).ConfigureAwait(false); - await ClientCredsAcquireAndAssertTokenSourceAsync(app3, "S2", TokenSource.Cache).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app3, "S1", TokenSource.Cache, 2).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app3, "S2", TokenSource.Cache, 2).ConfigureAwait(false); + httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); + await ClientCredsAcquireAndAssertTokenSourceAsync(app3, "S3", TokenSource.IdentityProvider, 3).ConfigureAwait(false); httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); - await ClientCredsAcquireAndAssertTokenSourceAsync(app_withoutStaticCache, "S1", TokenSource.IdentityProvider).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app_withoutStaticCache, "S1", TokenSource.IdentityProvider, 1).ConfigureAwait(false); httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(); - await ClientCredsAcquireAndAssertTokenSourceAsync(app_withoutStaticCache, "S2", TokenSource.IdentityProvider).ConfigureAwait(false); + await ClientCredsAcquireAndAssertTokenSourceAsync(app_withoutStaticCache, "S2", TokenSource.IdentityProvider, 2).ConfigureAwait(false); } } @@ -145,9 +147,12 @@ public async Task PublicClient_StaticCache_Async() .AcquireTokenInteractive(TestConstants.s_scope) .ExecuteAsync().ConfigureAwait(false); + Assert.AreEqual(1, result.AuthenticationResultMetadata.CachedAccessTokenCount); + var accounts = await app1.GetAccountsAsync().ConfigureAwait(false); Assert.AreEqual(1, accounts.Count()); result = await app1.AcquireTokenSilent(TestConstants.s_scope, accounts.Single()).ExecuteAsync().ConfigureAwait(false); + Assert.AreEqual(1, result.AuthenticationResultMetadata.CachedAccessTokenCount); var app2 = PublicClientApplicationBuilder .Create(TestConstants.ClientId) @@ -159,17 +164,29 @@ public async Task PublicClient_StaticCache_Async() accounts = await app2.GetAccountsAsync().ConfigureAwait(false); Assert.AreEqual(1, accounts.Count()); result = await app2.AcquireTokenSilent(TestConstants.s_scope, accounts.Single()).ExecuteAsync().ConfigureAwait(false); + Assert.AreEqual(1, result.AuthenticationResultMetadata.CachedAccessTokenCount); } } - private async Task ClientCredsAcquireAndAssertTokenSourceAsync(IConfidentialClientApplication app, string scope, TokenSource expectedSource) + private async Task ClientCredsAcquireAndAssertTokenSourceAsync( + IConfidentialClientApplication app, + string scope, + TokenSource expectedSource, + int expectedAccessTokenCount) { var result = await app.AcquireTokenForClient(new[] { scope }) .WithTenantId(TestConstants.Utid) .ExecuteAsync().ConfigureAwait(false); + Assert.AreEqual( expectedSource, result.AuthenticationResultMetadata.TokenSource); + + + Assert.AreEqual(expectedAccessTokenCount, + result.AuthenticationResultMetadata.CachedAccessTokenCount); + + return result; } } } diff --git a/tests/Microsoft.Identity.Test.Unit/CacheTests/LoadingProjectsTests.cs b/tests/Microsoft.Identity.Test.Unit/CacheTests/LoadingProjectsTests.cs index 4f7fc5b791..c22bda6088 100644 --- a/tests/Microsoft.Identity.Test.Unit/CacheTests/LoadingProjectsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/CacheTests/LoadingProjectsTests.cs @@ -8,7 +8,6 @@ namespace Microsoft.Identity.Test.Unit.CacheTests { -#if !ANDROID && !iOS // custom token cache serialization not available [TestClass] public class LoadingProjectsTests { @@ -27,5 +26,4 @@ public void CanDeserializeTokenCache() }; } } -#endif } diff --git a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs index 2c5b1b44ac..c8f41faaec 100644 --- a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs @@ -121,9 +121,13 @@ public async Task AcquireTokenForClient_ConcurrentTenantRequests_Test() // Wait for all tasks to complete AuthenticationResult[] results = await Task.WhenAll(tasks).ConfigureAwait(false); + int[] accessTokenCounts = results.Select(r => r.AuthenticationResultMetadata.CachedAccessTokenCount).ToArray(); // Assert the total tasks Assert.AreEqual(NumberOfRequests, results.Length, "Number of AuthenticationResult objects does not match the number of requests."); + + var expected = Enumerable.Range(1, NumberOfRequests).ToArray(); + CollectionAssert.AreEquivalent(expected, accessTokenCounts); } [TestMethod] From 63a822df2ca933a97d7ff5fe9adb80bb1bb0ea4f Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 6 Jun 2025 23:49:17 +0100 Subject: [PATCH 02/10] undo --- .../AcquireTokenForClientCacheTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs index d3b07222a1..4016ec9c3d 100644 --- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs @@ -44,9 +44,8 @@ public class AcquireTokenForClientCacheTests (10000, 10), }; - //[ParamsAllValues] - public bool EnableCacheSerialization { get; set; } = true; - + [ParamsAllValues] + public bool EnableCacheSerialization { get; set; }; //[Params(false)] public bool UseMicrosoftIdentityWebCache { get; set; } From ae1f034f2879e83f43e7c31c06b11e7ba9085803 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 6 Jun 2025 23:49:28 +0100 Subject: [PATCH 03/10] undo --- .../AcquireTokenForClientCacheTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs index 4016ec9c3d..915ff0c597 100644 --- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs @@ -55,8 +55,7 @@ public async Task GlobalSetupAsync() _cca = ConfidentialClientApplicationBuilder .Create(TestConstants.ClientId) .WithRedirectUri(TestConstants.RedirectUri) - .WithClientSecret(TestConstants.ClientSecret) - .WithCacheOptions(CacheOptions.EnableSharedCacheOptions) + .WithClientSecret(TestConstants.ClientSecret) .WithLegacyCacheCompatibility(false) .BuildConcrete(); From 8e27c2a0b42e1f1e4ca80b75b495fdef89d46de3 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 6 Jun 2025 23:50:42 +0100 Subject: [PATCH 04/10] Better msg --- .../AcquireTokenForClientCacheTests.cs | 2 +- tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs index 915ff0c597..d9d7db54fa 100644 --- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs @@ -45,7 +45,7 @@ public class AcquireTokenForClientCacheTests }; [ParamsAllValues] - public bool EnableCacheSerialization { get; set; }; + public bool EnableCacheSerialization { get; set; } //[Params(false)] public bool UseMicrosoftIdentityWebCache { get; set; } diff --git a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs index c8f41faaec..15cd620845 100644 --- a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs @@ -127,7 +127,7 @@ public async Task AcquireTokenForClient_ConcurrentTenantRequests_Test() Assert.AreEqual(NumberOfRequests, results.Length, "Number of AuthenticationResult objects does not match the number of requests."); var expected = Enumerable.Range(1, NumberOfRequests).ToArray(); - CollectionAssert.AreEquivalent(expected, accessTokenCounts); + CollectionAssert.AreEquivalent(expected, accessTokenCounts, "Each result should have a different number of tokens, because no cache hit occurs"); } [TestMethod] From 09b1de466173184a43e95a935109878481abe808 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Tue, 10 Jun 2025 19:30:18 +0100 Subject: [PATCH 05/10] Address PR comments --- ...nMemoryPartitionedAppTokenCacheAccessor.cs | 41 ++++++------------ ...MemoryPartitionedUserTokenCacheAccessor.cs | 42 +++++++------------ .../AcquireTokenForClientCacheTests.cs | 2 +- .../ParallelRequestsTests.cs | 4 +- 4 files changed, 30 insertions(+), 59 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs index 886dc2cb31..1a732eb856 100644 --- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedAppTokenCacheAccessor.cs @@ -38,8 +38,8 @@ internal class InMemoryPartitionedAppTokenCacheAccessor : ITokenCacheAccessor private int _entryCount = 0; private static int s_entryCount = 0; - public int EntryCount => _tokenCacheAccessorOptions.UseSharedCache ? s_entryCount : _entryCount; - + public int EntryCount => GetEntryCountRef(); + public InMemoryPartitionedAppTokenCacheAccessor( ILoggerAdapter logger, CacheOptions tokenCacheAccessorOptions) @@ -67,21 +67,14 @@ public void SaveAccessToken(MsalAccessTokenCacheItem item) var partition = AccessTokenCacheDictionary.GetOrAdd(partitionKey, _ => new ConcurrentDictionary()); bool added = partition.TryAdd(itemKey, item); + + // only increment the entry count if the item was added, not updated if (added) { - if (_tokenCacheAccessorOptions.UseSharedCache) - { - Interlocked.Increment(ref s_entryCount); - } - else - { - Interlocked.Increment(ref _entryCount); - } + Interlocked.Increment(ref GetEntryCountRef()); } else { - // If not added, it means it was replaced. Only increment if it was a new key. - // If you want to treat replacement as not changing the count, do nothing here. partition[itemKey] = item; } } @@ -156,14 +149,7 @@ public void DeleteAccessToken(MsalAccessTokenCacheItem item) bool removed = partition.TryRemove(item.CacheKey, out _); if (removed) { - if (_tokenCacheAccessorOptions.UseSharedCache) - { - Interlocked.Decrement(ref s_entryCount); - } - else - { - Interlocked.Decrement(ref _entryCount); - } + Interlocked.Decrement(ref GetEntryCountRef()); } else { @@ -255,14 +241,7 @@ public virtual void Clear(ILoggerAdapter requestlogger = null) { var logger = requestlogger ?? _logger; AccessTokenCacheDictionary.Clear(); - if (_tokenCacheAccessorOptions.UseSharedCache) - { - Interlocked.Exchange(ref s_entryCount, 0); - } - else - { - Interlocked.Exchange(ref _entryCount, 0); - } + Interlocked.Exchange(ref GetEntryCountRef(), 0); logger.Always("[Internal cache] Clearing app token cache accessor."); // app metadata isn't removable } @@ -271,5 +250,11 @@ public virtual bool HasAccessOrRefreshTokens() { return AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer())); } + + private ref int GetEntryCountRef() + { + return ref _tokenCacheAccessorOptions.UseSharedCache ? ref s_entryCount : ref _entryCount; + } + } } diff --git a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs index 4c895314b5..0d23372704 100644 --- a/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/InMemoryPartitionedUserTokenCacheAccessor.cs @@ -48,7 +48,7 @@ internal class InMemoryPartitionedUserTokenCacheAccessor : ITokenCacheAccessor private int _entryCount = 0; - public int EntryCount => _tokenCacheAccessorOptions.UseSharedCache ? s_entryCount : _entryCount; + public int EntryCount => GetEntryCountRef(); public InMemoryPartitionedUserTokenCacheAccessor(ILoggerAdapter logger, CacheOptions tokenCacheAccessorOptions) { @@ -81,16 +81,10 @@ public void SaveAccessToken(MsalAccessTokenCacheItem item) var partition = AccessTokenCacheDictionary.GetOrAdd(partitionKey, _ => new ConcurrentDictionary()); bool added = partition.TryAdd(itemKey, item); + // only increment the entry count if this is a new item if (added) { - if (_tokenCacheAccessorOptions.UseSharedCache) - { - System.Threading.Interlocked.Increment(ref s_entryCount); - } - else - { - System.Threading.Interlocked.Increment(ref _entryCount); - } + System.Threading.Interlocked.Increment(ref GetEntryCountRef()); } else { @@ -174,17 +168,10 @@ public void DeleteAccessToken(MsalAccessTokenCacheItem item) if (AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition)) { - bool removed = partition.TryRemove(item.CacheKey, out _); + bool removed = partition.TryRemove(item.CacheKey, out _); if (removed) { - if (_tokenCacheAccessorOptions.UseSharedCache) - { - System.Threading.Interlocked.Decrement(ref s_entryCount); - } - else - { - System.Threading.Interlocked.Decrement(ref _entryCount); - } + System.Threading.Interlocked.Decrement(ref GetEntryCountRef()); } else { @@ -281,7 +268,7 @@ public virtual List GetAllRefreshTokens(string partit if (RefreshTokenCacheDictionary.Count == 1 && result.Count == 0) { logger.VerbosePii( - () => $"[Internal cache] 0 RTs and 1 partition. Partition in cache is {RefreshTokenCacheDictionary.Keys.First()}", + () => $"[Internal cache] 0 RTs and 1 partition. Partition in cache is {RefreshTokenCacheDictionary.Keys.First()}", () => "[Internal cache] 0 RTs and 1 partition] 0 RTs and 1 partition."); } } @@ -325,7 +312,7 @@ public virtual List GetAllAccounts(string partitionKey = n { AccountCacheDictionary.TryGetValue(partitionKey, out ConcurrentDictionary partition); result = partition?.Select(kv => kv.Value)?.ToList() ?? CollectionHelpers.GetEmptyList(); - + if (logger.IsLoggingEnabled(LogLevel.Verbose)) { logger.Verbose(() => $"[Internal cache] GetAllAccounts (with partition - exists? {partition != null}) found {result.Count} accounts."); @@ -361,15 +348,9 @@ public virtual void Clear(ILoggerAdapter requestlogger = null) RefreshTokenCacheDictionary.Clear(); IdTokenCacheDictionary.Clear(); AccountCacheDictionary.Clear(); - if (_tokenCacheAccessorOptions.UseSharedCache) - { - System.Threading.Interlocked.Exchange(ref s_entryCount, 0); - } - else - { - System.Threading.Interlocked.Exchange(ref _entryCount, 0); - } // app metadata isn't removable + System.Threading.Interlocked.Exchange(ref GetEntryCountRef(), 0); + } /// WARNING: this API is slow as it loads all tokens, not just from 1 partition. @@ -379,5 +360,10 @@ public virtual bool HasAccessOrRefreshTokens() return RefreshTokenCacheDictionary.Any(partition => partition.Value.Count > 0) || AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer())); } + + private ref int GetEntryCountRef() + { + return ref _tokenCacheAccessorOptions.UseSharedCache ? ref s_entryCount : ref _entryCount; + } } } diff --git a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs index d9d7db54fa..548d6a330d 100644 --- a/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs +++ b/tests/Microsoft.Identity.Test.Performance/AcquireTokenForClientCacheTests.cs @@ -55,7 +55,7 @@ public async Task GlobalSetupAsync() _cca = ConfidentialClientApplicationBuilder .Create(TestConstants.ClientId) .WithRedirectUri(TestConstants.RedirectUri) - .WithClientSecret(TestConstants.ClientSecret) + .WithClientSecret(TestConstants.ClientSecret) .WithLegacyCacheCompatibility(false) .BuildConcrete(); diff --git a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs index 15cd620845..71f05bef03 100644 --- a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs @@ -126,8 +126,8 @@ public async Task AcquireTokenForClient_ConcurrentTenantRequests_Test() // Assert the total tasks Assert.AreEqual(NumberOfRequests, results.Length, "Number of AuthenticationResult objects does not match the number of requests."); - var expected = Enumerable.Range(1, NumberOfRequests).ToArray(); - CollectionAssert.AreEquivalent(expected, accessTokenCounts, "Each result should have a different number of tokens, because no cache hit occurs"); + int[] expectedTokenCounts = [.. Enumerable.Range(1, NumberOfRequests)]; + CollectionAssert.AreEquivalent(expectedTokenCounts, accessTokenCounts, "Each result should have a different number of tokens, because no cache hit occurs"); } [TestMethod] From bf34669bccb00b4b57399dabec960ca0ee65253c Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Tue, 10 Jun 2025 22:41:47 +0100 Subject: [PATCH 06/10] fix --- .../Platforms/Android/AndroidTokenCacheAccessor.cs | 2 ++ .../Platforms/iOS/iOSTokenCacheAccessor.cs | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs index 47eb369779..500f22d05d 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs @@ -161,6 +161,8 @@ public List GetAllAccounts(string optionalPartitionKey = n } #endregion + int EntryCount { get; } = 0; // not implemented for Android + public MsalAccountCacheItem GetAccount(MsalAccountCacheItem accountCacheItem) { return MsalAccountCacheItem.FromJsonString(_accountSharedPreference.GetString(accountCacheItem.CacheKey, null)); diff --git a/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs index 47dddc5cdb..46f64e4a5f 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs @@ -167,7 +167,9 @@ public List GetAllAccounts(string optionalPartitionKey = n .Select(x => MsalAccountCacheItem.FromJsonString(x)) .ToList(); } -#endregion + #endregion + + int EntryCount { get; } = 0; // not implemented for iOS internal SecStatusCode TryGetBrokerApplicationToken(string clientId, out string appToken) { From 34e1d01731539245eba95e67968bec71a4bdacad Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 13 Jun 2025 13:25:55 +0100 Subject: [PATCH 07/10] fix --- .../Platforms/Android/AndroidTokenCacheAccessor.cs | 2 +- .../Platforms/iOS/iOSTokenCacheAccessor.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs index 500f22d05d..cf501fe35f 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/Android/AndroidTokenCacheAccessor.cs @@ -161,7 +161,7 @@ public List GetAllAccounts(string optionalPartitionKey = n } #endregion - int EntryCount { get; } = 0; // not implemented for Android + public int EntryCount { get; } = 0; // not implemented for Android public MsalAccountCacheItem GetAccount(MsalAccountCacheItem accountCacheItem) { diff --git a/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs b/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs index 46f64e4a5f..89892e874a 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/iOS/iOSTokenCacheAccessor.cs @@ -169,7 +169,8 @@ public List GetAllAccounts(string optionalPartitionKey = n } #endregion - int EntryCount { get; } = 0; // not implemented for iOS + public int EntryCount { get; } = 0; // not implemented for iOS + internal SecStatusCode TryGetBrokerApplicationToken(string clientId, out string appToken) { From 1703c2b7a4c03776fd509b24513406e7868b7a99 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 13 Jun 2025 14:10:10 +0100 Subject: [PATCH 08/10] Update metadata --- src/client/Microsoft.Identity.Client/AuthenticationResult.cs | 1 - .../Microsoft.Identity.Client/Cache/CacheSessionManager.cs | 4 ++-- .../Internal/Requests/RequestBase.cs | 1 + .../TelemetryCore/Internal/Events/ApiEvent.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index 489b13936a..c75c921375 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -167,7 +167,6 @@ internal AuthenticationResult( CorrelationId = correlationID; ApiEvent = apiEvent; AuthenticationResultMetadata = new AuthenticationResultMetadata(tokenSource); - AuthenticationResultMetadata.CachedAccessTokenCount = apiEvent.CacheAccessTokenCount; AdditionalResponseParameters = msalAccessTokenCacheItem?.PersistedCacheParameters?.Count > 0 ? (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters : additionalResponseParameters; diff --git a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs index e47e131662..d7c1334385 100644 --- a/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs +++ b/src/client/Microsoft.Identity.Client/Cache/CacheSessionManager.cs @@ -51,7 +51,7 @@ public async Task FindAccessTokenAsync() public async Task> SaveTokenResponseAsync(MsalTokenResponse tokenResponse) { var result = await TokenCacheInternal.SaveTokenResponseAsync(_requestParams, tokenResponse).ConfigureAwait(false); - RequestContext.ApiEvent.CacheAccessTokenCount = TokenCacheInternal.Accessor.EntryCount; + RequestContext.ApiEvent.CachedAccessTokenCount = TokenCacheInternal.Accessor.EntryCount; return result; } @@ -184,7 +184,7 @@ private async Task RefreshCacheForReadOperationsAsync() RequestContext.ApiEvent.CacheLevel = CacheLevel.L1Cache; } - RequestContext.ApiEvent.CacheAccessTokenCount = TokenCacheInternal.Accessor.EntryCount; + RequestContext.ApiEvent.CachedAccessTokenCount = TokenCacheInternal.Accessor.EntryCount; } } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 69a3cbb876..0ac5bd3627 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -223,6 +223,7 @@ private void UpdateTelemetry(long elapsedMilliseconds, ApiEvent apiEvent, Authen authenticationResult.AuthenticationResultMetadata.CacheLevel = GetCacheLevel(authenticationResult); authenticationResult.AuthenticationResultMetadata.Telemetry = apiEvent.MsalRuntimeTelemetry; authenticationResult.AuthenticationResultMetadata.RegionDetails = CreateRegionDetails(apiEvent); + authenticationResult.AuthenticationResultMetadata.CachedAccessTokenCount = apiEvent.CachedAccessTokenCount; Metrics.IncrementTotalDurationInMs(authenticationResult.AuthenticationResultMetadata.DurationTotalInMs); } diff --git a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs index 053bfded3b..abd5bac980 100644 --- a/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs +++ b/src/client/Microsoft.Identity.Client/TelemetryCore/Internal/Events/ApiEvent.cs @@ -62,7 +62,7 @@ public string ApiIdString public string ApiErrorCode { get; set; } - public int CacheAccessTokenCount { get; set; } + public int CachedAccessTokenCount { get; set; } #region Region public string RegionUsed { get; set; } From 16f03fcad4c53c87447c5c7f11c91311371ad9bd Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Fri, 13 Jun 2025 15:42:47 +0100 Subject: [PATCH 09/10] remove test --- tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs index 71f05bef03..2c5b1b44ac 100644 --- a/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ParallelRequestsTests.cs @@ -121,13 +121,9 @@ public async Task AcquireTokenForClient_ConcurrentTenantRequests_Test() // Wait for all tasks to complete AuthenticationResult[] results = await Task.WhenAll(tasks).ConfigureAwait(false); - int[] accessTokenCounts = results.Select(r => r.AuthenticationResultMetadata.CachedAccessTokenCount).ToArray(); // Assert the total tasks Assert.AreEqual(NumberOfRequests, results.Length, "Number of AuthenticationResult objects does not match the number of requests."); - - int[] expectedTokenCounts = [.. Enumerable.Range(1, NumberOfRequests)]; - CollectionAssert.AreEquivalent(expectedTokenCounts, accessTokenCounts, "Each result should have a different number of tokens, because no cache hit occurs"); } [TestMethod] From 634e4a592d4876a4a25eb8bbb188c02338dd8d2e Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Mon, 16 Jun 2025 15:49:08 +0100 Subject: [PATCH 10/10] Update AuthenticationResultMetadata.cs --- .../Microsoft.Identity.Client/AuthenticationResultMetadata.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs b/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs index e19ac5ddd7..ec07ecb171 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResultMetadata.cs @@ -99,8 +99,7 @@ public AuthenticationResultMetadata(TokenSource tokenSource) public string Telemetry { get; set; } /// - /// The number of entries in the token cache. For app tokens, this is the number of access tokens cached. - /// For users tokens, an entry contains all tokens for a user, including refresh token and id tokens. + /// The number of access tokens in the cache. /// public int CachedAccessTokenCount { get; set; } }