//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using System.Collections.Generic; using System.Threading; namespace System.IdentityModel { /// /// A cache of type T where items are cached and removed /// according to the type specified, currently only 'TimeBounded' is supported. An items is added with an expiration time. /// internal class BoundedCache where T : class { Dictionary> _items; int _capacity; TimeSpan _purgeInterval; ReaderWriterLock _readWriteLock; DateTime _nextPurgeTime = DateTime.UtcNow; /// /// Creates a cache for items of Type 'T' where expired items will purged on a regular interval /// /// The maximum size of the cache in number of items. /// If int.MaxValue is passed then the size is not bound. /// The time interval for checking expired items. /// public BoundedCache(int capacity, TimeSpan purgeInterval) : this(capacity, purgeInterval, StringComparer.Ordinal) { } /// /// Creates a cache for items of Type 'T' where expired items will purged on a regular interval /// /// The maximum size of the cache in number of items. /// If int.MaxValue is passed then the size is not bound. /// The time interval for checking expired items. /// EqualityComparer for comparing keys. /// The input parameter 'capacity' is less than or equal to zero. /// The input parameter 'purgeInterval' is less than or equal to TimeSpan.Zero. /// The input parameter 'keyComparer' is null. public BoundedCache(int capacity, TimeSpan purgeInterval, IEqualityComparer keyComparer) { if (capacity <= 0) { throw DiagnosticUtility.ThrowHelperArgumentOutOfRange("capacity", capacity, SR.GetString(SR.ID0002)); } if (purgeInterval <= TimeSpan.Zero) { throw DiagnosticUtility.ThrowHelperArgumentOutOfRange("purgeInterval", purgeInterval, SR.GetString(SR.ID0016)); } if (keyComparer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyComparer"); } _capacity = capacity; _purgeInterval = purgeInterval; _items = new Dictionary>(keyComparer); _readWriteLock = new ReaderWriterLock(); } /// /// Gets the ReaderWriterLock for controlling simultaneous reads and writes /// protected ReaderWriterLock CacheLock { get { return _readWriteLock; } } /// /// Gets or Sets the current Capacity of the cache in number of items. /// public virtual int Capacity { get { return _capacity; } set { if (value <= 0) { throw DiagnosticUtility.ThrowHelperArgumentOutOfRange("value", value, SR.GetString(SR.ID0002)); } _capacity = value; } } /// /// Removes all items from the Cache. /// public virtual void Clear() { // -1 milleseconds is infinite timeout _readWriteLock.AcquireWriterLock(TimeSpan.FromMilliseconds(-1)); try { _items.Clear(); } finally { _readWriteLock.ReleaseWriterLock(); } } /// /// Ensures that the maximum size is not exceeded /// /// If the Capacity of the cache has been reached. void EnforceQuota() { // int.MaxValue => unbounded if (_capacity == int.MaxValue) { return; } if (_items.Count >= _capacity) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new LimitExceededException(SR.GetString(SR.ID0021, _capacity))); } } /// /// Increases the maximum number of items that the cache will hold. /// /// The capacity to increase. /// The input parameter 'size' is less than or equal to zero. /// Updated capacity /// If size + current capacity >= int.MaxValue then capacity will be set to int.MaxValue and the cache will be unbounded public virtual int IncreaseCapacity(int size) { if (size <= 0) { throw DiagnosticUtility.ThrowHelperArgumentOutOfRange("size", size, SR.GetString(SR.ID0002)); } // -1 milleseconds is infinite timeout _readWriteLock.AcquireWriterLock(TimeSpan.FromMilliseconds(-1)); try { if (int.MaxValue - size <= _capacity) { _capacity = int.MaxValue; } else { _capacity = _capacity + size; } return _capacity; } finally { _readWriteLock.ReleaseWriterLock(); } } /// /// Gets the Dictionary that contains the cached items. /// protected Dictionary> Items { get { return _items; } } /// /// 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. /// void Purge() { DateTime currentTime = DateTime.UtcNow; if (currentTime < _nextPurgeTime) { return; } _nextPurgeTime = DateTimeUtil.Add(currentTime, _purgeInterval); // -1 milleseconds is infinite timeout _readWriteLock.AcquireWriterLock(TimeSpan.FromMilliseconds(-1)); try { List expiredItems = new List(); foreach (string key in _items.Keys) { if (_items[key].IsExpired()) { expiredItems.Add(key); } } for (int i = 0; i < expiredItems.Count; ++i) { _items.Remove(expiredItems[i]); } } finally { _readWriteLock.ReleaseWriterLock(); } } /// /// Gets or Sets the time interval that will be used for checking expired items. /// /// If 'value' is less than or equal to TimeSpan.Zero. public TimeSpan PurgeInterval { get { return _purgeInterval; } set { if (value <= TimeSpan.Zero) { throw DiagnosticUtility.ThrowHelperArgumentOutOfRange("value", value, SR.GetString(SR.ID0016)); } _purgeInterval = value; } } /// /// Attempt to add a item to the cache. /// /// Key to use when adding item /// Item of type 'T' to add to cache /// The expiration time of the entry. /// true if item was added, false if item was not added /// Thrown if an attempt is made to add an item when the current /// cache size is equal to the capacity public virtual bool TryAdd(string key, T item, DateTime expirationTime) { Purge(); // -1 milleseconds is infinite timeout _readWriteLock.AcquireWriterLock(TimeSpan.FromMilliseconds(-1)); EnforceQuota(); try { if (_items.ContainsKey(key)) { return false; } else { _items[key] = new ExpirableItem(item, expirationTime); return true; } } finally { _readWriteLock.ReleaseWriterLock(); } } /// /// Attempts to find an item in the cache /// /// Item to search for. /// true if item is in cache, false otherwise /// Item may be expired and would be purged next cycle public virtual bool TryFind(string key) { Purge(); // -1 milleseconds is infinite timeout _readWriteLock.AcquireReaderLock(TimeSpan.FromMilliseconds(-1)); try { if (_items.ContainsKey(key) && !_items[key].IsExpired()) { return true; } return false; } finally { _readWriteLock.ReleaseReaderLock(); } } /// /// Attempt to get an item from the Cache /// /// Item to seach for /// The object refernece that will be set the the retrivied item. /// true if item is found, false otherwise /// Item may be expired and would be purged next cycle public virtual bool TryGet(string key, out T item) { Purge(); item = null; // -1 milleseconds is infinite timeout _readWriteLock.AcquireReaderLock(TimeSpan.FromMilliseconds(-1)); try { if (_items.ContainsKey(key)) { if (!_items[key].IsExpired()) { item = _items[key].Item; return true; } } return false; } finally { _readWriteLock.ReleaseReaderLock(); } } /// /// Attempts to remove an item from the Cache /// /// Item to remove /// true if item was removed, false otherwise public virtual bool TryRemove(string key) { Purge(); // -1 milleseconds is infinite timeout _readWriteLock.AcquireWriterLock(TimeSpan.FromMilliseconds(-1)); try { if (!_items.ContainsKey(key)) { return false; } _items.Remove(key); return true; } finally { _readWriteLock.ReleaseWriterLock(); } } /// /// Wrapper class for objects contained in BoundedCache. Contains the obj 'T' and /// /// Type of the item protected class ExpirableItem { DateTime _expirationTime; ET _item; public ExpirableItem(ET item, DateTime expirationTime) { _item = item; if (expirationTime.Kind != DateTimeKind.Utc) { _expirationTime = DateTimeUtil.ToUniversalTime(expirationTime); } else { _expirationTime = expirationTime; } } public bool IsExpired() { return (_expirationTime <= DateTime.UtcNow); } public ET Item { get { return _item; } } } [Flags] internal enum CachingMode { Time, MRU, FIFO } } }