-
Notifications
You must be signed in to change notification settings - Fork 404
[Prototype] Add cache size limit and eviction. #3104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
caf1827
5f4559d
64f8718
ccb06a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
|
|
||
| namespace Microsoft.Identity.Client | ||
| { | ||
| /// <summary> | ||
|
|
@@ -39,6 +41,17 @@ public CacheOptions(bool useSharedCache) | |
| UseSharedCache = useSharedCache; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Constructor | ||
| /// </summary> | ||
| /// <param name="useSharedCache">Set to true to share the cache between all ClientApplication objects. The cache becomes static. <see cref="UseSharedCache"/> for a detailed description. </param> | ||
| /// <param name="sizeLimit">Token cache size limit in bytes. <see cref="SizeLimit"/> for a detailed description.</param> | ||
| public CacheOptions(bool useSharedCache, long sizeLimit) | ||
| { | ||
| UseSharedCache = useSharedCache; | ||
| SizeLimit = sizeLimit; | ||
| } | ||
|
pmaytak marked this conversation as resolved.
|
||
|
|
||
|
pmaytak marked this conversation as resolved.
|
||
| /// <summary> | ||
| /// Share the cache between all ClientApplication objects. The cache becomes static. Defaults to false. | ||
| /// </summary> | ||
|
|
@@ -50,5 +63,37 @@ public CacheOptions(bool useSharedCache) | |
| /// </remarks> | ||
| public bool UseSharedCache { get; set; } | ||
|
|
||
| private long? _sizeLimit; | ||
|
|
||
| /// <summary> | ||
| /// Total token cache size limit in bytes for both app and user token caches. | ||
|
pmaytak marked this conversation as resolved.
|
||
| /// </summary> | ||
| /// <remarks> | ||
| /// Once the limit is reached, either the app or user cache will be fully cleared, depending on which was most recently used. | ||
| /// MSAL doesn't calculate the exact memory usage and uses approximations of the token sizes. | ||
| /// For instance, app token cache entry is approximately at least 4500 bytes; user access token entry - 6500 bytes, | ||
| /// user refresh token entry - 3700 bytes. | ||
| /// Using a MemoryCache via Microsoft.Identity.Web.TokenCache is more accurate but slower. | ||
| /// This size limit applies only to internal memory cache and is not a concern when distributed caching is used. | ||
| /// IMPORTANT: Monitor app health metrics (including memory usage) and cache performance (<see href="https://aka.ms/msal-net-token-cache-serialization"/>) | ||
| /// and adjust size limit accordingly. | ||
| /// </remarks> | ||
| public long? SizeLimit | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Size limit applies only when cache serialization is disabled. |
||
| { | ||
| get => _sizeLimit; | ||
| set | ||
| { | ||
| ValidateSizeLimit(value); | ||
| _sizeLimit = value; | ||
| } | ||
| } | ||
|
|
||
| private void ValidateSizeLimit(long? sizeLimit) | ||
| { | ||
| if (sizeLimit < 0) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(sizeLimit), $"{nameof(sizeLimit)} must be a positive number."); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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.Cache.Keys; | ||
|
|
@@ -35,6 +36,11 @@ internal class InMemoryPartitionedAppTokenCacheAccessor : ITokenCacheAccessor | |
| protected readonly ICoreLogger _logger; | ||
| private readonly CacheOptions _tokenCacheAccessorOptions; | ||
|
|
||
| // Approximate size of cache item objects | ||
| private const long AccessTokenSizeInBytes = 4500; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My main concern is that a constant approximation will not work well:
I think there are a few ways out of this:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dannybtsai @henrik-me - can you please review this strategy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason to use a constant value instead of the actual size (the #2 option)?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@dannybtsai |
||
|
|
||
| private long _appCacheSize; | ||
|
|
||
| public InMemoryPartitionedAppTokenCacheAccessor( | ||
| ICoreLogger logger, | ||
| CacheOptions tokenCacheAccessorOptions) | ||
|
|
@@ -57,12 +63,26 @@ public InMemoryPartitionedAppTokenCacheAccessor( | |
| #region Add | ||
| public void SaveAccessToken(MsalAccessTokenCacheItem item) | ||
| { | ||
| if (IsCacheOverCapacity(AccessTokenSizeInBytes)) | ||
| { | ||
| _logger.Always("[AppCache] Cache is over capacity."); | ||
| Compact(); | ||
| } | ||
|
|
||
| string itemKey = item.GetKey().ToString(); | ||
| string partitionKey = CacheKeyFactory.GetClientCredentialKey(item.ClientId, item.TenantId, item.KeyId); | ||
|
|
||
| // Update cache size only if cache item is added, not updated | ||
| if (!AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition) || !partition.TryGetValue(itemKey, out _)) | ||
| { | ||
| Interlocked.Add(ref _appCacheSize, AccessTokenSizeInBytes); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it really necessary to use a variable to track the size? Isn't _appCacheSize == AccessTokenCacheDictionary.Count * AccessTokenSizeInBytes? There seems to be some risk of not setting the size correctly even though Interlocked methods are atomic. And it is more complicated to set them in the Clear() method without locking.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If AccessTokenSizeInBytes is added to the _appCacheSize every time a token is saved, isn't _appCacheSize == AccessTokenCacheDictionary.Count * AccessTokenSizeInBytes? |
||
| Interlocked.Add(ref TokenCache.CacheSize, AccessTokenSizeInBytes); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I don't think so. There are two instances of TokenCache class created and then one instance has app token cache accessor and one has user accessor, but they don't know about each other. And I don't think CacheSize can be moved into a higher parent class. |
||
| } | ||
|
|
||
| // if a conflict occurs, pick the latest value | ||
| AccessTokenCacheDictionary | ||
| .GetOrAdd(partitionKey, new ConcurrentDictionary<string, MsalAccessTokenCacheItem>())[itemKey] = item; | ||
| _logger.Verbose($"[AppCache] Saved access token. App cache size: {Interlocked.Read(ref _appCacheSize)}."); | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -134,9 +154,14 @@ public void DeleteAccessToken(MsalAccessTokenCacheItem item) | |
| if (partition == null || !partition.TryRemove(item.GetKey().ToString(), out _)) | ||
| { | ||
| _logger.InfoPii( | ||
| $"Cannot delete access token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "Cannot delete access token because it was not found in the cache."); | ||
| $"[AppCache] Cannot delete access token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "[AppCache] Cannot delete access token because it was not found in the cache."); | ||
| return; | ||
| } | ||
|
|
||
| Interlocked.Add(ref _appCacheSize, -AccessTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -AccessTokenSizeInBytes); | ||
| _logger.Verbose($"[AppCache] Removed access token. App cache size: {Interlocked.Read(ref _appCacheSize)}."); | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -175,7 +200,7 @@ public void DeleteAccount(MsalAccountCacheItem item) | |
| /// </summary> | ||
| public virtual IReadOnlyList<MsalAccessTokenCacheItem> GetAllAccessTokens(string partitionKey = null) | ||
| { | ||
| _logger.Always($"[GetAllAccessTokens] Total number of cache partitions found while getting access tokens: {AccessTokenCacheDictionary.Count}"); | ||
| _logger.Always($"[AppCache] Total number of cache partitions found while getting access tokens: {AccessTokenCacheDictionary.Count}"); | ||
| if (string.IsNullOrEmpty(partitionKey)) | ||
| { | ||
| return AccessTokenCacheDictionary.SelectMany(dict => dict.Value).Select(kv => kv.Value).ToList(); | ||
|
|
@@ -216,13 +241,26 @@ public void SetiOSKeychainSecurityGroup(string keychainSecurityGroup) | |
| public virtual void Clear() | ||
| { | ||
| AccessTokenCacheDictionary.Clear(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Threading issue: Between Clear and setting CacheSize to zero, something may get added to the dictionary and the two can go out of sync
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, but we also don't lock on cache operations in general (based on eventual consistency principle). So this behavior can happen with other add/remove methods. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eventual consistency is not guaranteed here. For example:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dannybtsai Do you suggest locking? Only when clearing or always when updating the cache size? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is hard to achieve consistency without locking as far as I can tell. And you need to always lock, not just when clearing cache otherwise it is the same problem. |
||
| _logger.Always("[Clear] Clearing access token cache data."); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -_appCacheSize); | ||
| Interlocked.Exchange(ref _appCacheSize, 0); | ||
| _logger.Always("[AppCache] Cleared access token cache data."); | ||
| // app metadata isn't removable | ||
| } | ||
|
|
||
| public virtual bool HasAccessOrRefreshTokens() | ||
| { | ||
| return AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer())); | ||
| } | ||
|
|
||
| private bool IsCacheOverCapacity(long sizeToAdd) | ||
| { | ||
| return _tokenCacheAccessorOptions.SizeLimit.HasValue && (Interlocked.Read(ref TokenCache.CacheSize) + sizeToAdd) > _tokenCacheAccessorOptions.SizeLimit; | ||
| } | ||
|
|
||
| private void Compact() | ||
|
SameerK-MSFT marked this conversation as resolved.
|
||
| { | ||
| _logger.Always("[AppCache] Compacting cache."); | ||
| Clear(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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.Cache.Keys; | ||
|
|
@@ -44,6 +45,14 @@ internal class InMemoryPartitionedUserTokenCacheAccessor : ITokenCacheAccessor | |
| protected readonly ICoreLogger _logger; | ||
| private readonly CacheOptions _tokenCacheAccessorOptions; | ||
|
|
||
| // Approximate size of cache item objects | ||
| private const long AccessTokenSizeInBytes = 6500; | ||
| private const long RefreshTokenSizeInBytes = 3700; | ||
| private const long IDTokenSizeInBytes = 3300; | ||
| private const long AccountSizeInBytes = 1300; | ||
|
|
||
| private long _userCacheSize; | ||
|
|
||
| public InMemoryPartitionedUserTokenCacheAccessor(ICoreLogger logger, CacheOptions tokenCacheAccessorOptions) | ||
| { | ||
| _logger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||
|
|
@@ -70,38 +79,82 @@ public InMemoryPartitionedUserTokenCacheAccessor(ICoreLogger logger, CacheOption | |
| #region Add | ||
| public void SaveAccessToken(MsalAccessTokenCacheItem item) | ||
| { | ||
| // When saving tokens in SaveTokenResponseAsync, AT is saved first, then other tokens. | ||
| // For the user cache, checking size limit and compacting only when saving AT, | ||
| // because otherwise, if compact is run in other save methods, | ||
| // it could leave unassociated tokens in the cache. | ||
| // (For ex, compact in SaveAccount, would leave only account in the cache, without other tokens.) | ||
| if (IsCacheOverCapacity(AccessTokenSizeInBytes)) | ||
| { | ||
| _logger.Always("[UserCache] Cache is over capacity."); | ||
| Compact(); | ||
|
pmaytak marked this conversation as resolved.
|
||
| } | ||
|
|
||
| string itemKey = item.GetKey().ToString(); | ||
| string partitionKey = CacheKeyFactory.GetKeyFromCachedItem(item); | ||
|
|
||
| // Update cache size only if cache item is added, not updated | ||
| if (!AccessTokenCacheDictionary.TryGetValue(partitionKey, out var partition) || !partition.TryGetValue(itemKey, out _)) | ||
| { | ||
| Interlocked.Add(ref _userCacheSize, AccessTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, AccessTokenSizeInBytes); | ||
| } | ||
|
|
||
| // if a conflict occurs, pick the latest value | ||
| AccessTokenCacheDictionary | ||
| .GetOrAdd(partitionKey, new ConcurrentDictionary<string, MsalAccessTokenCacheItem>())[itemKey] = item; // if a conflict occurs, pick the latest value | ||
| .GetOrAdd(partitionKey, new ConcurrentDictionary<string, MsalAccessTokenCacheItem>())[itemKey] = item; | ||
| _logger.Verbose($"[UserCache] Saved access token. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void SaveRefreshToken(MsalRefreshTokenCacheItem item) | ||
| { | ||
| string itemKey = item.GetKey().ToString(); | ||
| string partitionKey = CacheKeyFactory.GetKeyFromCachedItem(item); | ||
|
|
||
| // Update cache size only if cache item is added, not updated | ||
| if (!RefreshTokenCacheDictionary.TryGetValue(partitionKey, out var partition) || !partition.TryGetValue(itemKey, out _)) | ||
| { | ||
| Interlocked.Add(ref _userCacheSize, RefreshTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, RefreshTokenSizeInBytes); | ||
| } | ||
|
|
||
| RefreshTokenCacheDictionary | ||
| .GetOrAdd(partitionKey, new ConcurrentDictionary<string, MsalRefreshTokenCacheItem>())[itemKey] = item; | ||
| _logger.Verbose($"[UserCache] Saved refresh token. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void SaveIdToken(MsalIdTokenCacheItem item) | ||
| { | ||
| string itemKey = item.GetKey().ToString(); | ||
| string partitionKey = CacheKeyFactory.GetKeyFromCachedItem(item); | ||
|
|
||
| // Update cache size only if cache item is added, not updated | ||
| if (!IdTokenCacheDictionary.TryGetValue(partitionKey, out var partition) || !partition.TryGetValue(itemKey, out _)) | ||
| { | ||
| Interlocked.Add(ref _userCacheSize, IDTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, IDTokenSizeInBytes); | ||
| } | ||
|
|
||
| IdTokenCacheDictionary | ||
| .GetOrAdd(partitionKey, new ConcurrentDictionary<string, MsalIdTokenCacheItem>())[itemKey] = item; | ||
| _logger.Verbose($"[UserCache] Saved ID token. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void SaveAccount(MsalAccountCacheItem item) | ||
| { | ||
| string itemKey = item.GetKey().ToString(); | ||
| string partitionKey = CacheKeyFactory.GetKeyFromCachedItem(item); | ||
|
|
||
| // Update cache size only if cache item is added, not updated | ||
| if (!AccountCacheDictionary.TryGetValue(partitionKey, out var partition) || !partition.TryGetValue(itemKey, out _)) | ||
| { | ||
| Interlocked.Add(ref _userCacheSize, AccountSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, AccountSizeInBytes); | ||
| } | ||
|
|
||
| AccountCacheDictionary | ||
| .GetOrAdd(partitionKey, new ConcurrentDictionary<string, MsalAccountCacheItem>())[itemKey] = item; | ||
| _logger.Verbose($"[UserCache] Saved account. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void SaveAppMetadata(MsalAppMetadataCacheItem item) | ||
|
|
@@ -155,9 +208,14 @@ public void DeleteAccessToken(MsalAccessTokenCacheItem item) | |
| if (partition == null || !partition.TryRemove(item.GetKey().ToString(), out _)) | ||
| { | ||
| _logger.InfoPii( | ||
| $"Cannot delete access token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "Cannot delete access token because it was not found in the cache."); | ||
| $"[UserCache] Cannot delete access token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "[UserCache] Cannot delete access token because it was not found in the cache."); | ||
| return; | ||
| } | ||
|
|
||
| Interlocked.Add(ref _userCacheSize, -AccessTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -AccessTokenSizeInBytes); | ||
| _logger.Verbose($"[UserCache] Removed access token. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void DeleteRefreshToken(MsalRefreshTokenCacheItem item) | ||
|
|
@@ -168,9 +226,14 @@ public void DeleteRefreshToken(MsalRefreshTokenCacheItem item) | |
| if (partition == null || !partition.TryRemove(item.GetKey().ToString(), out _)) | ||
| { | ||
| _logger.InfoPii( | ||
| $"Cannot delete refresh token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "Cannot delete refresh token because it was not found in the cache."); | ||
| $"[UserCache] Cannot delete refresh token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "[UserCache] Cannot delete refresh token because it was not found in the cache."); | ||
| return; | ||
| } | ||
|
|
||
| Interlocked.Add(ref _userCacheSize, -RefreshTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -RefreshTokenSizeInBytes); | ||
| _logger.Verbose($"[UserCache] Removed refresh token. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void DeleteIdToken(MsalIdTokenCacheItem item) | ||
|
|
@@ -181,9 +244,14 @@ public void DeleteIdToken(MsalIdTokenCacheItem item) | |
| if (partition == null || !partition.TryRemove(item.GetKey().ToString(), out _)) | ||
| { | ||
| _logger.InfoPii( | ||
| $"Cannot delete ID token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "Cannot delete ID token because it was not found in the cache."); | ||
| $"[UserCache] Cannot delete ID token because it was not found in the cache. Key {item.GetKey()}.", | ||
| "[UserCache] Cannot delete ID token because it was not found in the cache."); | ||
| return; | ||
| } | ||
|
|
||
| Interlocked.Add(ref _userCacheSize, -IDTokenSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -IDTokenSizeInBytes); | ||
| _logger.Verbose($"[UserCache] Removed ID token. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
|
|
||
| public void DeleteAccount(MsalAccountCacheItem item) | ||
|
|
@@ -194,9 +262,14 @@ public void DeleteAccount(MsalAccountCacheItem item) | |
| if (partition == null || !partition.TryRemove(item.GetKey().ToString(), out _)) | ||
| { | ||
| _logger.InfoPii( | ||
| $"Cannot delete account because it was not found in the cache. Key {item.GetKey()}.", | ||
| "Cannot delete account because it was not found in the cache"); | ||
| $"[UserCache] Cannot delete account because it was not found in the cache. Key {item.GetKey()}.", | ||
| "[UserCache] Cannot delete account because it was not found in the cache"); | ||
| return; | ||
| } | ||
|
|
||
| Interlocked.Add(ref _userCacheSize, -AccountSizeInBytes); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -AccountSizeInBytes); | ||
| _logger.Verbose($"[UserCache] Removed account. User cache size: {Interlocked.Read(ref _userCacheSize)}."); | ||
| } | ||
| #endregion | ||
|
|
||
|
|
@@ -277,11 +350,13 @@ public void SetiOSKeychainSecurityGroup(string keychainSecurityGroup) | |
|
|
||
| public virtual void Clear() | ||
| { | ||
| _logger.Always("[Clear] Clearing access token cache data."); | ||
| AccessTokenCacheDictionary.Clear(); | ||
| RefreshTokenCacheDictionary.Clear(); | ||
| IdTokenCacheDictionary.Clear(); | ||
| AccountCacheDictionary.Clear(); | ||
| Interlocked.Add(ref TokenCache.CacheSize, -_userCacheSize); | ||
| Interlocked.Exchange(ref _userCacheSize, 0); | ||
| _logger.Always("[UserCache] Cleared access token cache data."); | ||
| // app metadata isn't removable | ||
| } | ||
|
|
||
|
|
@@ -292,5 +367,16 @@ public virtual bool HasAccessOrRefreshTokens() | |
| return RefreshTokenCacheDictionary.Any(partition => partition.Value.Count > 0) || | ||
| AccessTokenCacheDictionary.Any(partition => partition.Value.Any(token => !token.Value.IsExpiredWithBuffer())); | ||
| } | ||
|
|
||
| private bool IsCacheOverCapacity(long sizeToAdd) | ||
| { | ||
| return _tokenCacheAccessorOptions.SizeLimit.HasValue && (Interlocked.Read(ref TokenCache.CacheSize) + sizeToAdd) > _tokenCacheAccessorOptions.SizeLimit; | ||
| } | ||
|
|
||
| private void Compact() | ||
| { | ||
| _logger.Always("[UserCache] Compacting cache."); | ||
| Clear(); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clearing the cache because it's simpler and other forms of compaction become complex for the user cache. (For ex. removing only ATs will leave the user cache with non-AT tokens; trying to remove all tokens for a user doesn't work for OBO since partition keys are different; randomly removing token entries will leave orphaned/unassociated tokens.) |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,11 @@ public sealed partial class TokenCache : ITokenCacheInternal | |
| /// </summary> | ||
| internal bool UsesDefaultSerialization { get; set; } = false; | ||
|
|
||
| /// <summary> | ||
| /// Static, used by both app and user caches to track approximate token cache size. | ||
| /// </summary> | ||
| internal static long CacheSize = 0L; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cache size includes user+ app token cache. 2 instances of TokenCache class are created (1 with app cache accessors, 1 with user accessors). So moved this size property up a level so it can be accessed by both. However, because of this design, clearing the cache is only done for that respective cache, app or user. |
||
|
|
||
| internal string ClientId => ServiceBundle.Config.ClientId; | ||
|
|
||
| ITokenCacheAccessor ITokenCacheInternal.Accessor => Accessor; | ||
|
|
@@ -117,7 +122,7 @@ public void SetIosKeychainSecurityGroup(string securityGroup) | |
| (LegacyCachePersistence as Microsoft.Identity.Client.Platforms.iOS.iOSLegacyCachePersistence).SetKeychainSecurityGroup(securityGroup); | ||
| #endif | ||
| } | ||
|
|
||
| private void UpdateAppMetadata(string clientId, string environment, string familyId) | ||
| { | ||
| if (_featureFlags.IsFociEnabled) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have default values here (as per any recommended practices)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can provide a realistic default value; it would be based on the user's scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's a good question we're struggling with :). What would be a good default? ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If default varies widely based on the user scenarios, we cannot do much. But if it primarly leans towards a certain value, that can be used. But we do not have answer, then it is good as it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Danny also touched on a point of should there bit a minimum. Currently if someone specifies small value (like 1), cache will be compacted on each operation.