352 lines
14 KiB
C#
352 lines
14 KiB
C#
|
//-----------------------------------------------------------------------
|
||
|
// <copyright file="MruSessionSecurityTokenCache.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//-----------------------------------------------------------------------
|
||
|
|
||
|
namespace System.IdentityModel.Tokens
|
||
|
{
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.ObjectModel;
|
||
|
using System.Diagnostics;
|
||
|
using System.IdentityModel.Diagnostics;
|
||
|
using System.IdentityModel.Tokens;
|
||
|
|
||
|
/// <summary>
|
||
|
/// An MRU cache (Most Recently Used).
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Thread safe. Critsec around each method.
|
||
|
/// A LinkedList is used to track MRU for fast purge.
|
||
|
/// A Dictionary is used for fast keyed lookup.
|
||
|
/// Grows until it reaches this.maximumSize, then purges down to this.sizeAfterPurge.
|
||
|
/// </remarks>
|
||
|
internal class MruSessionSecurityTokenCache : SessionSecurityTokenCache
|
||
|
{
|
||
|
#pragma warning disable 1591
|
||
|
public const int DefaultTokenCacheSize = 20000;
|
||
|
public static readonly TimeSpan DefaultPurgeInterval = TimeSpan.FromMinutes(15);
|
||
|
#pragma warning restore 1591
|
||
|
|
||
|
private DateTime nextPurgeTime = DateTime.UtcNow + DefaultPurgeInterval;
|
||
|
private Dictionary<SessionSecurityTokenCacheKey, CacheEntry> items;
|
||
|
private int maximumSize;
|
||
|
private CacheEntry mruEntry;
|
||
|
private LinkedList<SessionSecurityTokenCacheKey> mruList;
|
||
|
private int sizeAfterPurge;
|
||
|
private object syncRoot = new object();
|
||
|
private object purgeLock = new object();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor to create an instance of this class.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Uses the default maximum cache size.
|
||
|
/// </remarks>
|
||
|
public MruSessionSecurityTokenCache()
|
||
|
: this(DefaultTokenCacheSize)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor to create an instance of this class.
|
||
|
/// </summary>
|
||
|
/// <param name="maximumSize">Defines the maximum size of the cache.</param>
|
||
|
public MruSessionSecurityTokenCache(int maximumSize)
|
||
|
: this(maximumSize, null)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor to create an instance of this class.
|
||
|
/// </summary>
|
||
|
/// <param name="maximumSize">Defines the maximum size of the cache.</param>
|
||
|
/// <param name="comparer">The method used for comparing cache entries.</param>
|
||
|
public MruSessionSecurityTokenCache(int maximumSize, IEqualityComparer<SessionSecurityTokenCacheKey> comparer)
|
||
|
: this((maximumSize / 5) * 4, maximumSize, comparer)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor to create an instance of this class.
|
||
|
/// </summary>
|
||
|
/// <param name="sizeAfterPurge">
|
||
|
/// If the cache size exceeds <paramref name="maximumSize"/>,
|
||
|
/// the cache will be resized to <paramref name="sizeAfterPurge"/> by removing least recently used items.
|
||
|
/// </param>
|
||
|
/// <param name="maximumSize">Defines the maximum size of the cache.</param>
|
||
|
public MruSessionSecurityTokenCache(int sizeAfterPurge, int maximumSize)
|
||
|
: this(sizeAfterPurge, maximumSize, null)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor to create an instance of this class.
|
||
|
/// </summary>
|
||
|
/// <param name="sizeAfterPurge">Specifies the size to which the cache is purged after it reaches <paramref name="maximumSize"/>.</param>
|
||
|
/// <param name="maximumSize">Specifies the maximum size of the cache.</param>
|
||
|
/// <param name="comparer">Specifies the method used for comparing cache entries.</param>
|
||
|
public MruSessionSecurityTokenCache(int sizeAfterPurge, int maximumSize, IEqualityComparer<SessionSecurityTokenCacheKey> comparer)
|
||
|
{
|
||
|
if (sizeAfterPurge < 0)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.ID0008), "sizeAfterPurge"));
|
||
|
}
|
||
|
|
||
|
if (sizeAfterPurge >= maximumSize)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.ID0009), "sizeAfterPurge"));
|
||
|
}
|
||
|
|
||
|
// null comparer is ok
|
||
|
this.items = new Dictionary<SessionSecurityTokenCacheKey, CacheEntry>(maximumSize, comparer);
|
||
|
this.maximumSize = maximumSize;
|
||
|
this.mruList = new LinkedList<SessionSecurityTokenCacheKey>();
|
||
|
this.sizeAfterPurge = sizeAfterPurge;
|
||
|
this.mruEntry = new CacheEntry();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the maximum size of the cache
|
||
|
/// </summary>
|
||
|
public int MaximumSize
|
||
|
{
|
||
|
get { return this.maximumSize; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Deletes the specified cache entry from the MruCache.
|
||
|
/// </summary>
|
||
|
/// <param name="key">Specifies the key for the entry to be deleted.</param>
|
||
|
/// <exception cref="ArgumentNullException">The <paramref name="key"/> is null.</exception>
|
||
|
public override void Remove(SessionSecurityTokenCacheKey key)
|
||
|
{
|
||
|
if (key == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
CacheEntry entry;
|
||
|
if (this.items.TryGetValue(key, out entry))
|
||
|
{
|
||
|
this.items.Remove(key);
|
||
|
this.mruList.Remove(entry.Node);
|
||
|
if (object.ReferenceEquals(this.mruEntry.Node, entry.Node))
|
||
|
{
|
||
|
this.mruEntry.Value = null;
|
||
|
this.mruEntry.Node = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Attempts to add an entry to the cache or update an existing one.
|
||
|
/// </summary>
|
||
|
/// <param name="key">The key for the entry to be added.</param>
|
||
|
/// <param name="value">The security token to be added to the cache.</param>
|
||
|
/// <param name="expirationTime">The expiration time for this entry.</param>
|
||
|
public override void AddOrUpdate(SessionSecurityTokenCacheKey key, SessionSecurityToken value, DateTime expirationTime)
|
||
|
{
|
||
|
if (key == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("key");
|
||
|
}
|
||
|
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
this.Purge();
|
||
|
this.Remove(key);
|
||
|
|
||
|
// Add the new entry to the cache and make it the MRU element
|
||
|
CacheEntry entry = new CacheEntry();
|
||
|
entry.Node = this.mruList.AddFirst(key);
|
||
|
entry.Value = value;
|
||
|
this.items.Add(key, entry);
|
||
|
this.mruEntry = entry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns the Session Security Token corresponding to the specified key exists in the cache. Also if it exists, marks it as MRU.
|
||
|
/// </summary>
|
||
|
/// <param name="key">Specifies the key for the entry to be retrieved.</param>
|
||
|
/// <returns>Returns the Session Security Token from the cache if found, otherwise, null.</returns>
|
||
|
public override SessionSecurityToken Get(SessionSecurityTokenCacheKey key)
|
||
|
{
|
||
|
if (key == null)
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// If found, make the entry most recently used
|
||
|
SessionSecurityToken sessionToken = null;
|
||
|
CacheEntry entry;
|
||
|
bool found;
|
||
|
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
// first check our MRU item
|
||
|
if (this.mruEntry.Node != null && key != null && key.Equals(this.mruEntry.Node.Value))
|
||
|
{
|
||
|
return this.mruEntry.Value;
|
||
|
}
|
||
|
|
||
|
found = this.items.TryGetValue(key, out entry);
|
||
|
if (found)
|
||
|
{
|
||
|
sessionToken = entry.Value;
|
||
|
|
||
|
// Move the node to the head of the MRU list if it's not already there
|
||
|
if (this.mruList.Count > 1 && !object.ReferenceEquals(this.mruList.First, entry.Node))
|
||
|
{
|
||
|
this.mruList.Remove(entry.Node);
|
||
|
this.mruList.AddFirst(entry.Node);
|
||
|
this.mruEntry = entry;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return sessionToken;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Deletes matching cache entries from the MruCache.
|
||
|
/// </summary>
|
||
|
/// <param name="endpointId">Specifies the endpointId for the entries to be deleted.</param>
|
||
|
/// <param name="contextId">Specifies the contextId for the entries to be deleted.</param>
|
||
|
public override void RemoveAll(string endpointId, System.Xml.UniqueId contextId)
|
||
|
{
|
||
|
if (null == contextId || string.IsNullOrEmpty(endpointId))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Dictionary<SessionSecurityTokenCacheKey, CacheEntry> entriesToDelete = new Dictionary<SessionSecurityTokenCacheKey, CacheEntry>();
|
||
|
SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(endpointId, contextId, null);
|
||
|
key.IgnoreKeyGeneration = true;
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
foreach (SessionSecurityTokenCacheKey itemKey in this.items.Keys)
|
||
|
{
|
||
|
if (itemKey.Equals(key))
|
||
|
{
|
||
|
entriesToDelete.Add(itemKey, this.items[itemKey]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (SessionSecurityTokenCacheKey itemKey in entriesToDelete.Keys)
|
||
|
{
|
||
|
this.items.Remove(itemKey);
|
||
|
CacheEntry entry = entriesToDelete[itemKey];
|
||
|
this.mruList.Remove(entry.Node);
|
||
|
if (object.ReferenceEquals(this.mruEntry.Node, entry.Node))
|
||
|
{
|
||
|
this.mruEntry.Value = null;
|
||
|
this.mruEntry.Node = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Attempts to remove all entries with a matching endpoint Id from the cache.
|
||
|
/// </summary>
|
||
|
/// <param name="endpointId">The endpoint id for the entry to be removed.</param>
|
||
|
public override void RemoveAll(string endpointId)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException(SR.GetString(SR.ID4294)));
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Returns all the entries that match the given key.
|
||
|
/// </summary>
|
||
|
/// <param name="endpointId">The endpoint id for the entries to be retrieved.</param>
|
||
|
/// <param name="contextId">The context id for the entries to be retrieved.</param>
|
||
|
/// <returns>A collection of all the matching entries, an empty collection of no match found.</returns>
|
||
|
public override IEnumerable<SessionSecurityToken> GetAll(string endpointId, System.Xml.UniqueId contextId)
|
||
|
{
|
||
|
Collection<SessionSecurityToken> tokens = new Collection<SessionSecurityToken>();
|
||
|
|
||
|
if (null == contextId || string.IsNullOrEmpty(endpointId))
|
||
|
{
|
||
|
return tokens;
|
||
|
}
|
||
|
|
||
|
CacheEntry entry;
|
||
|
SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(endpointId, contextId, null);
|
||
|
key.IgnoreKeyGeneration = true;
|
||
|
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
foreach (SessionSecurityTokenCacheKey itemKey in this.items.Keys)
|
||
|
{
|
||
|
if (itemKey.Equals(key))
|
||
|
{
|
||
|
entry = this.items[itemKey];
|
||
|
|
||
|
// Move the node to the head of the MRU list if it's not already there
|
||
|
if (this.mruList.Count > 1 && !object.ReferenceEquals(this.mruList.First, entry.Node))
|
||
|
{
|
||
|
this.mruList.Remove(entry.Node);
|
||
|
this.mruList.AddFirst(entry.Node);
|
||
|
this.mruEntry = entry;
|
||
|
}
|
||
|
|
||
|
tokens.Add(entry.Value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return tokens;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This method must not be called from within a read or writer lock as a deadlock will occur.
|
||
|
/// Checks the time a decides if a cleanup needs to occur.
|
||
|
/// </summary>
|
||
|
private void Purge()
|
||
|
{
|
||
|
if (this.items.Count >= this.maximumSize)
|
||
|
{
|
||
|
// If the cache is full, purge enough LRU items to shrink the
|
||
|
// cache down to the low watermark
|
||
|
int countToPurge = this.maximumSize - this.sizeAfterPurge;
|
||
|
for (int i = 0; i < countToPurge; i++)
|
||
|
{
|
||
|
SessionSecurityTokenCacheKey keyRemove = this.mruList.Last.Value;
|
||
|
this.mruList.RemoveLast();
|
||
|
this.items.Remove(keyRemove);
|
||
|
}
|
||
|
|
||
|
if (DiagnosticUtility.ShouldTrace(TraceEventType.Information))
|
||
|
{
|
||
|
TraceUtility.TraceString(
|
||
|
TraceEventType.Information,
|
||
|
SR.GetString(
|
||
|
SR.ID8003,
|
||
|
this.maximumSize,
|
||
|
this.sizeAfterPurge));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class CacheEntry
|
||
|
{
|
||
|
public SessionSecurityToken Value
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
|
||
|
public LinkedListNode<SessionSecurityTokenCacheKey> Node
|
||
|
{
|
||
|
get;
|
||
|
set;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|